From 00b7d33bea30348ed7347352a3851981820bb16f Mon Sep 17 00:00:00 2001 From: danceratopz Date: Tue, 9 Aug 2022 17:25:19 +0200 Subject: [PATCH 1/4] tests: add a test case for #31 This test case first creates the state described in the ticket as a setup step, ie, that the lowest scored TCO2 is not present in the NCT pool. Then it tests that autoRetire works, even if the lowest scored TCO2 is not in the pool. --- test/OffsetHelper.ts | 85 +++++++++++++++++++++++++++++++++++++++----- utils/addresses.ts | 11 +++++- 2 files changed, 86 insertions(+), 10 deletions(-) diff --git a/test/OffsetHelper.ts b/test/OffsetHelper.ts index d99bec9..7730d13 100644 --- a/test/OffsetHelper.ts +++ b/test/OffsetHelper.ts @@ -11,6 +11,7 @@ import { import * as hardhatContracts from "../utils/toucanContracts.json"; import * as poolContract from "../artifacts/contracts/interfaces/IToucanPoolToken.sol/IToucanPoolToken.json"; +import * as carbonOffsetsContract from "../artifacts/contracts/interfaces/IToucanCarbonOffsets.sol/IToucanCarbonOffsets.json"; import { IToucanPoolToken, OffsetHelper, @@ -18,7 +19,7 @@ import { Swapper, Swapper__factory, } from "../typechain"; -import addresses from "../utils/addresses"; +import addresses, { whaleAddresses } from "../utils/addresses"; import { Contract } from "ethers"; import { usdcABI, wethABI, wmaticABI } from "../utils/ABIs"; @@ -85,13 +86,31 @@ describe("Offset Helper - autoOffset", function () { ] ); - await Promise.all( - addrs.map(async (addr) => { - await addr.sendTransaction({ - to: addr2.address, - value: (await addr.getBalance()).sub(parseEther("1.0")), - }); - }) + // Transfer a large amount of MATIC and NCT to the test account via account impersonation. + await network.provider.request({ + method: "hardhat_impersonateAccount", + params: [whaleAddresses.matic], + }); + const maticWhale = ethers.provider.getSigner(whaleAddresses.matic); + await maticWhale.sendTransaction({ + to: addr2.address, + value: (await maticWhale.getBalance()).sub(parseEther("1.0")), + }); + + // Note: The swapper fails when trying to exchange such a large amount of NCT. + await network.provider.request({ + method: "hardhat_impersonateAccount", + params: [whaleAddresses.nct], + }); + const addrNctWhale = ethers.provider.getSigner(whaleAddresses.nct); + const nctWhaleSigner = new ethers.Contract( + addresses.nct, + hardhatContracts.contracts.NatureCarbonTonne.abi, + addrNctWhale + ); + await nctWhaleSigner.transfer( + addr2.address, + await nctWhaleSigner.balanceOf(whaleAddresses.nct) ); await swapper.swap(addresses.weth, parseEther("20.0"), { @@ -298,7 +317,7 @@ describe("Offset Helper - autoOffset", function () { ).to.be.revertedWith("Insufficient TCO2 balance"); }); - it("Should retire using an NCT deposit", async function () { + it("Should retire using a BCT deposit", async function () { await (await bct.approve(offsetHelper.address, parseEther("1.0"))).wait(); await ( @@ -319,6 +338,54 @@ describe("Offset Helper - autoOffset", function () { await expect(offsetHelper.autoRetire(tco2s, amounts)).to.not.be.reverted; }); + + it("Should retire using an NCT deposit, even if the first scored TCO2 is not in pool", async function () { + const scoredTCO2s = await nct.getScoredTCO2s(); + const lowestScoredTCO2 = new ethers.Contract( + scoredTCO2s[0], + carbonOffsetsContract.abi, + addr2 + ); + const lowestScoredTCO2Balance = await lowestScoredTCO2.balanceOf( + addresses.nct + ); + + // Skip setup if the oldest tco2's balance in the pool is already 0. + if (formatEther(lowestScoredTCO2Balance) !== "0.0") { + // Setup: If the oldest tco2 balance is non-zero, remove all its tokens from the pool via a redeem. + // Ensure that addr2 has enough NCT to redeem all of the lowestScoredTCO2 or setup will fail. + expect(await nct.balanceOf(addr2.address)).to.be.above( + await lowestScoredTCO2.balanceOf(addresses.nct) + ); + + await nct.approve(offsetHelper.address, lowestScoredTCO2Balance); + + await offsetHelper.deposit(addresses.nct, lowestScoredTCO2Balance); + + await offsetHelper.autoRedeem(addresses.nct, lowestScoredTCO2Balance); + } + + // Ensure the test condition is met. + expect(await lowestScoredTCO2.balanceOf(addresses.nct)).to.equal(0); + + await nct.approve(offsetHelper.address, parseEther("1.0")); + + await offsetHelper.deposit(addresses.nct, parseEther("0.0005")); + + const redeemReceipt = await ( + await offsetHelper.autoRedeem(addresses.nct, parseEther("0.0005")) + ).wait(); + + if (!redeemReceipt.events) { + return; + } + const tco2s = + redeemReceipt.events[redeemReceipt.events.length - 1].args?.tco2s; + const amounts = + redeemReceipt.events[redeemReceipt.events.length - 1].args?.amounts; + + await expect(offsetHelper.autoRetire(tco2s, amounts)).to.not.be.reverted; + }); }); describe("Testing deposit() and withdraw()", function () { diff --git a/utils/addresses.ts b/utils/addresses.ts index d3ed614..d88becf 100644 --- a/utils/addresses.ts +++ b/utils/addresses.ts @@ -7,7 +7,11 @@ interface IfcAddresses { wmatic: string; } -const addresses: IfcAddresses = { +interface IfcWhaleAddresses { + matic: string; + nct: string; +} +export const addresses: IfcAddresses = { myAddress: "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", bct: "0x2F800Db0fdb5223b3C3f354886d907A671414A7F", nct: "0xD838290e877E0188a4A44700463419ED96c16107", @@ -16,6 +20,11 @@ const addresses: IfcAddresses = { wmatic: "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270", }; +export const whaleAddresses: IfcWhaleAddresses = { + matic: "0xe7804c37c13166fF0b37F5aE0BB07A3aEbb6e245", + nct: "0x4b3ebae392e8b90a9b13068e90b27d9c41abc3c8", +}; + export const mumbaiAddresses: IfcAddresses = { myAddress: "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", bct: "0xf2438A14f668b1bbA53408346288f3d7C71c10a1", From f0489cfb9ee09f259ff17729abb9592eef4530f3 Mon Sep 17 00:00:00 2001 From: danceratopz Date: Tue, 9 Aug 2022 17:25:46 +0200 Subject: [PATCH 2/4] fix: don't call retire if the tco2 amount is zero #31 --- contracts/OffsetHelper.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/contracts/OffsetHelper.sol b/contracts/OffsetHelper.sol index 3bde11b..9f0b785 100644 --- a/contracts/OffsetHelper.sol +++ b/contracts/OffsetHelper.sol @@ -488,7 +488,9 @@ contract OffsetHelper is OffsetHelperStorage { balances[msg.sender][_tco2s[i]] >= _amounts[i], "Insufficient TCO2 balance" ); - + if (_amounts[i] == 0) { + continue; + } balances[msg.sender][_tco2s[i]] -= _amounts[i]; IToucanCarbonOffsets(_tco2s[i]).retire(_amounts[i]); From d37cbd085c6d9db721d76cc1f2f738f6e2ae8e7c Mon Sep 17 00:00:00 2001 From: danceratopz Date: Tue, 9 Aug 2022 21:10:43 +0200 Subject: [PATCH 3/4] chore: enable github actions for pushes and prs on all branches Previously, a github action was triggered when pushing to a branch, but it the action ran with main. --- .github/workflows/ci.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a494ba1..bc33bf0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,10 +1,6 @@ name: Smart contracts -on: - push: - branches: [main] - pull_request: - branches: [main] +on: ["push", "pull_request"] jobs: build: From eb19d3a72aaeac3bb8512f97adc7629eed6f319b Mon Sep 17 00:00:00 2001 From: danceratopz Date: Mon, 22 Aug 2022 15:37:12 +0200 Subject: [PATCH 4/4] fix: increment counter in while loop, forgotten in f0489cfb --- contracts/OffsetHelper.sol | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/contracts/OffsetHelper.sol b/contracts/OffsetHelper.sol index 9f0b785..0b5d291 100644 --- a/contracts/OffsetHelper.sol +++ b/contracts/OffsetHelper.sol @@ -488,13 +488,10 @@ contract OffsetHelper is OffsetHelperStorage { balances[msg.sender][_tco2s[i]] >= _amounts[i], "Insufficient TCO2 balance" ); - if (_amounts[i] == 0) { - continue; + if (_amounts[i] > 0) { + balances[msg.sender][_tco2s[i]] -= _amounts[i]; + IToucanCarbonOffsets(_tco2s[i]).retire(_amounts[i]); } - balances[msg.sender][_tco2s[i]] -= _amounts[i]; - - IToucanCarbonOffsets(_tco2s[i]).retire(_amounts[i]); - unchecked { ++i; }