diff --git a/smart-contracts/.solcover.js b/smart-contracts/.solcover.js index df039369399..2d2a091f7b5 100644 --- a/smart-contracts/.solcover.js +++ b/smart-contracts/.solcover.js @@ -6,5 +6,6 @@ module.exports = { 'UnlockDiscountTokenV3', 'UnlockProtocolGovernor', 'utils', + 'CardPurchaser.sol', ], } diff --git a/smart-contracts/contracts/mixins/MixinFunds.sol b/smart-contracts/contracts/mixins/MixinFunds.sol index 6d947634d5e..7010132f126 100644 --- a/smart-contracts/contracts/mixins/MixinFunds.sol +++ b/smart-contracts/contracts/mixins/MixinFunds.sol @@ -27,7 +27,7 @@ contract MixinFunds is MixinErrors { function _isValidToken(address _tokenAddress) internal view { if ( _tokenAddress != address(0) && - IERC20Upgradeable(_tokenAddress).totalSupply() < 0 + IERC20Upgradeable(_tokenAddress).totalSupply() <= 0 ) { revert INVALID_TOKEN(); } diff --git a/smart-contracts/contracts/test-artifacts/TestERC20.sol b/smart-contracts/contracts/test-artifacts/TestERC20.sol index 26d8c98a640..4d49341c762 100644 --- a/smart-contracts/contracts/test-artifacts/TestERC20.sol +++ b/smart-contracts/contracts/test-artifacts/TestERC20.sol @@ -10,3 +10,26 @@ contract TestERC20 is ERC20 { _mint(holder, amount); } } + +contract TestERC20WithResultControl is TestERC20 { + bool result; + + function setResult(bool _result) public { + result = _result; + } + + function transfer( + address recipient, + uint256 amount + ) public override returns (bool) { + return result; + } + + function transferFrom( + address sender, + address recipient, + uint256 amount + ) public override returns (bool) { + return result; + } +} diff --git a/smart-contracts/contracts/test-artifacts/TestERC721Receiver.sol b/smart-contracts/contracts/test-artifacts/TestERC721Receiver.sol index f274e640761..8123c9853de 100644 --- a/smart-contracts/contracts/test-artifacts/TestERC721Receiver.sol +++ b/smart-contracts/contracts/test-artifacts/TestERC721Receiver.sol @@ -11,3 +11,14 @@ contract TestERC721Recevier { return bytes4(keccak256("onERC721Received(address,address,uint256,bytes)")); } } + +contract TestERC721RecevierWithWrongResult { + function onERC721Received( + address, + address, + uint256, + bytes calldata + ) external returns (bytes4) { + return bytes4(keccak256("")); + } +} diff --git a/smart-contracts/package.json b/smart-contracts/package.json index 5a6d5a930b0..43214cf3921 100644 --- a/smart-contracts/package.json +++ b/smart-contracts/package.json @@ -53,6 +53,7 @@ "lint": "yarn lint:contracts && yarn lint:code", "lintFix": "yarn lint:contracts --fix --noPrompt && yarn lint:code --fix", "coverage": "IS_COVERAGE=1 yarn hardhat coverage", + "coverage_fork": "IS_COVERAGE=1 RUN_FORK=1 yarn hardhat coverage --testfiles \"**/*.mainnet.js\"", "dev": "yarn lint && yarn build && yarn test", "size": "yarn hardhat size-contracts", "ci": "yarn lint && yarn test", diff --git a/smart-contracts/test/CardPurchaser/purchase.mainnet.js b/smart-contracts/test/CardPurchaser/purchase.mainnet.js index 6267b7fa0cf..fb5db804322 100644 --- a/smart-contracts/test/CardPurchaser/purchase.mainnet.js +++ b/smart-contracts/test/CardPurchaser/purchase.mainnet.js @@ -1,13 +1,11 @@ const { ethers } = require('hardhat') const assert = require('assert') +const { deployLock, reverts, ADDRESS_ZERO } = require('../helpers') + const { - deployLock, - getUnlockAddress, - reverts, - addSomeETH, addSomeUSDC, -} = require('../helpers') - + getUnlockAddress, +} = require('@unlock-protocol/hardhat-helpers') const USDC_ABI = require('@unlock-protocol/hardhat-helpers/dist/ABIs/USDC.json') const USDC = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' @@ -107,15 +105,13 @@ const signLockPurchase = async ({ } describe(`CardPurchaser / purchase (mainnet only)`, function () { - let chainId, unlock, cardPurchaser, signer, lock, unlockAddress + let chainId, unlock, cardPurchaser, signer, user, lock, unlockAddress before(async function () { if (!process.env.RUN_FORK) { // all suite will be skipped this.skip() } - ;[signer] = await ethers.getSigners() - - await addSomeETH(await signer.getAddress()) + ;[signer, user] = await ethers.getSigners() // get Unlock contract unlockAddress = await getUnlockAddress() @@ -205,7 +201,7 @@ describe(`CardPurchaser / purchase (mainnet only)`, function () { }) it('should fail if the payer is not the sender', async () => { - const notSender = new ethers.Wallet.createRandom() + const notSender = ethers.Wallet.createRandom() const transfer = await signUSDCTransfer({ chainId, signer, @@ -226,12 +222,12 @@ describe(`CardPurchaser / purchase (mainnet only)`, function () { purchase.signature, await purchaseCallData(lock, await signer.getAddress()) ), - 'PURCHASER_DOES_NOT_MATCH_PAYER()' + 'PURCHASER_DOES_NOT_MATCH_PAYER' ) }) it('should fail if the signature of the purchaseMessage does not match', async () => { - const notSigner = new ethers.Wallet.createRandom() + const notSigner = ethers.Wallet.createRandom() const transfer = await signUSDCTransfer({ chainId, signer, @@ -253,7 +249,7 @@ describe(`CardPurchaser / purchase (mainnet only)`, function () { purchase.signature, await purchaseCallData(lock, await signer.getAddress()) ), - 'SIGNER_DOES_NOT_MATCH()' + 'SIGNER_DOES_NOT_MATCH' ) }) @@ -285,7 +281,7 @@ describe(`CardPurchaser / purchase (mainnet only)`, function () { it('should fail if the transfer of tokens fails because the recipient is not correct!', async () => { await addSomeUSDC(USDC, await signer.getAddress(), keyPrice) - const notCardPurchaser = new ethers.Wallet.createRandom() + const notCardPurchaser = ethers.Wallet.createRandom() const transfer = await signUSDCTransfer({ chainId, @@ -416,4 +412,59 @@ describe(`CardPurchaser / purchase (mainnet only)`, function () { 0 ) }) + + describe('withdraw', () => { + it('only owner should be able to withdraw', async () => { + await reverts( + cardPurchaser + .connect(user) + .withdraw(USDC, await signer.getAddress(), 0), + 'OwnableUnauthorizedAccount' + ) + }) + + it('owner should be able to withdraw USDC from CardPurchaser', async () => { + const usdcContract = new ethers.Contract(USDC, USDC_ABI, signer) + const balanceBefore = await usdcContract.balanceOf( + await cardPurchaser.getAddress() + ) + console.log('balanceBefore =======', balanceBefore) + await cardPurchaser.withdraw( + USDC, + await signer.getAddress(), + balanceBefore + ) + assert.equal( + await usdcContract.balanceOf(await cardPurchaser.getAddress()), + 0 + ) + }) + + it('owner should be able to withdraw ETH from CardPurchaser', async () => { + const balanceBefore = await ethers.provider.getBalance( + await cardPurchaser.getAddress() + ) + console.log('balanceBefore =======', balanceBefore) + await cardPurchaser.withdraw( + ADDRESS_ZERO, + await signer.getAddress(), + balanceBefore + ) + assert.equal( + await ethers.provider.getBalance(await cardPurchaser.getAddress()), + 0 + ) + }) + + it('transfer too much ETH from cardpurchaser should fail', async () => { + await reverts( + cardPurchaser.withdraw( + ADDRESS_ZERO, + await signer.getAddress(), + ethers.parseEther('1') + ), + 'WITHDRAW_FAILED' + ) + }) + }) }) diff --git a/smart-contracts/test/Kickback/kickback.js b/smart-contracts/test/Kickback/kickback.js index 32f5a731b52..588dff92530 100644 --- a/smart-contracts/test/Kickback/kickback.js +++ b/smart-contracts/test/Kickback/kickback.js @@ -17,9 +17,6 @@ describe('Kickback contract', () => { before(async () => { const [deployer] = await ethers.getSigners() - // deploy unlock - const { unlock } = await deployContracts() - // create a new lock lock = await deployLock() const result = await purchaseKeys(lock, 5) diff --git a/smart-contracts/test/Lock/disableTransfers.js b/smart-contracts/test/Lock/disableTransfers.js index f3839307a47..a828d65eca3 100644 --- a/smart-contracts/test/Lock/disableTransfers.js +++ b/smart-contracts/test/Lock/disableTransfers.js @@ -17,6 +17,28 @@ describe('Lock / disableTransfers', () => { await lock.updateTransferFee(10000) }) + it('unauthorized user cannot transfer NFT', async () => { + await reverts( + lock.transferFrom( + await accountWithNoKey.getAddress(), + await accountWithNoKey.getAddress(), + tokenId + ), + 'UNAUTHORIZED' + ) + }) + + it('transfer NFT to the same address is not allowed', async () => { + await reverts( + lock.transferFrom( + await keyOwner.getAddress(), + await keyOwner.getAddress(), + tokenId + ), + 'TRANSFER_TO_SELF' + ) + }) + describe('setting fee to 100%', () => { describe('disabling transferFrom', () => { it('should prevent key transfers by reverting', async () => { diff --git a/smart-contracts/test/Lock/erc721/safeTransferFrom.js b/smart-contracts/test/Lock/erc721/safeTransferFrom.js index e5f1c8b3ae4..9f272775b47 100644 --- a/smart-contracts/test/Lock/erc721/safeTransferFrom.js +++ b/smart-contracts/test/Lock/erc721/safeTransferFrom.js @@ -65,6 +65,24 @@ describe('Lock / erc721 / safeTransferFrom', () => { assert.equal(ownerOf, await random.getAddress()) }) + it('should fail to transfer when a contract implements onERC721Received with Wrong Result', async () => { + ;({ tokenId } = await purchaseKey(lock, await random3.getAddress())) + // A contract which does implement onERC721Received: + const TestERC721Recevier = await ethers.getContractFactory( + 'TestERC721RecevierWithWrongResult' + ) + const nonCompliantContract = await TestERC721Recevier.deploy() + + await reverts( + lock + .connect(random3) + [ + safeTransferFromSig + ](await random3.getAddress(), await nonCompliantContract.getAddress(), tokenId), + 'NON_COMPLIANT_ERC721_RECEIVER' + ) + }) + it('should success to transfer when a contract implements onERC721Received', async () => { ;({ tokenId } = await purchaseKey(lock, await random3.getAddress())) // A contract which does implement onERC721Received: diff --git a/smart-contracts/test/Lock/erc721/tokenSymbol.js b/smart-contracts/test/Lock/erc721/tokenSymbol.js index ecc426d97bf..937615a8ac5 100644 --- a/smart-contracts/test/Lock/erc721/tokenSymbol.js +++ b/smart-contracts/test/Lock/erc721/tokenSymbol.js @@ -33,6 +33,10 @@ describe('Lock / erc721 / tokenSymbol', () => { ) }) + it('should return unlockToken Symbol if lock symbol is empty', async () => { + assert.equal(await lock.symbol(), await unlock.globalTokenSymbol()) + }) + it('should allow the owner to set the global token Symbol', async () => { assert.equal(await unlock.globalTokenSymbol(), 'KEY') }) diff --git a/smart-contracts/test/Lock/fail.js b/smart-contracts/test/Lock/fail.js new file mode 100644 index 00000000000..91e6867b5f1 --- /dev/null +++ b/smart-contracts/test/Lock/fail.js @@ -0,0 +1,15 @@ +const { ethers } = require('hardhat') +const { deployLock, reverts } = require('../helpers') + +describe('Lock / fail to deploy', async () => { + it('fail to deploy Lock if invalid ERC20 input', async () => { + const NonToken = await ethers.getContractFactory('TestERC20') + const nonToken = await NonToken.deploy() + await reverts( + deployLock({ + tokenAddress: await nonToken.getAddress(), + }), + 'INVALID_TOKEN' + ) + }) +}) diff --git a/smart-contracts/test/Lock/grantKeyExtension.js b/smart-contracts/test/Lock/grantKeyExtension.js index d3642143ea2..97f22c17f5a 100644 --- a/smart-contracts/test/Lock/grantKeyExtension.js +++ b/smart-contracts/test/Lock/grantKeyExtension.js @@ -40,6 +40,11 @@ describe('Lock / grantKeyExtension', () => { ;({ tokenId } = args) }) + it('schemaVersion has been updated', async () => { + await lock.updateSchemaVersion() + assert.equal(await lock.schemaVersion(), await lock.publicLockVersion()) + }) + describe('extend a valid key without a specific duration', () => { let tsBefore, args before(async () => { diff --git a/smart-contracts/test/Lock/hooks/CaptchaHook.js b/smart-contracts/test/Lock/hooks/CaptchaHook.js index f7fd0053d60..b42979c5ae4 100644 --- a/smart-contracts/test/Lock/hooks/CaptchaHook.js +++ b/smart-contracts/test/Lock/hooks/CaptchaHook.js @@ -1,8 +1,67 @@ const assert = require('assert') const { ethers } = require('hardhat') -const { reverts, deployLock } = require('../../helpers') +const { reverts, deployLock, ADDRESS_ZERO } = require('../../helpers') describe('CaptchaHook', function () { + it('random user should not be able to call addSigner and removeSigner', async function () { + const [_, user2] = await ethers.getSigners() + const secretSigner = ethers.Wallet.createRandom() + + const CaptchaHook = await ethers.getContractFactory('CaptchaHook') + const hook = await CaptchaHook.deploy() + + await reverts( + hook.connect(user2).addSigner(await secretSigner.getAddress()), + 'OwnableUnauthorizedAccount' + ) + await reverts( + hook.connect(user2).removeSigner(await secretSigner.getAddress()), + 'OwnableUnauthorizedAccount' + ) + }) + + it('owner should be able to call addSigner and removeSigner', async function () { + const [_, user2] = await ethers.getSigners() + const secretSigner = ethers.Wallet.createRandom() + + const CaptchaHook = await ethers.getContractFactory('CaptchaHook') + const hook = await CaptchaHook.deploy() + + await (await hook.addSigner(await secretSigner.getAddress())).wait() + const isSignerBefore = await hook.signers(await secretSigner.getAddress()) + assert.equal(isSignerBefore, true) + + await (await hook.removeSigner(await secretSigner.getAddress())).wait() + const isSignerAfter = await hook.signers(await secretSigner.getAddress()) + assert.equal(isSignerAfter, false) + }) + + it('keyPurchasePrice should be 0 if caller is not a contract', async () => { + const secretSigner = ethers.Wallet.createRandom() + const [user] = await ethers.getSigners() + + const CaptchaHook = await ethers.getContractFactory('CaptchaHook') + const hook = await CaptchaHook.deploy() + await (await hook.addSigner(await secretSigner.getAddress())).wait() + + const messageHash = ethers.solidityPackedKeccak256( + ['string'], + [(await user.getAddress()).toLowerCase()] + ) + const signedMessage = await secretSigner.signMessage( + ethers.getBytes(messageHash) + ) + + const price = await hook.keyPurchasePrice( + await user.getAddress(), + await user.getAddress(), + await user.getAddress(), + signedMessage + ) + + assert.equal(price, 0) + }) + it('Should work', async function () { const [user] = await ethers.getSigners() const secretSigner = ethers.Wallet.createRandom() @@ -125,4 +184,29 @@ describe('CaptchaHook', function () { 'ECDSAInvalidSignatureLength' ) }) + + it('toString - number', async () => { + const CaptchaHook = await ethers.getContractFactory('CaptchaHook') + const hook = await CaptchaHook.deploy() + + const toStringFunction = hook.getFunction( + 'function toString(uint256) public pure returns (string memory)' + ) + const value = 123 + const result = await toStringFunction(value) + assert.equal(result.includes(value.toString(16)), true) + }) + + it('toString - string', async () => { + const CaptchaHook = await ethers.getContractFactory('CaptchaHook') + const hook = await CaptchaHook.deploy() + + const value = `0x${ethers.parseEther('10').toString(16)}` + const hexString = ethers.hexlify(ethers.zeroPadValue(value, 32)) + const toStringFunction = hook.getFunction( + 'function toString(bytes32) public pure returns (string memory)' + ) + const result = await toStringFunction(hexString) + assert.equal(result, hexString) + }) }) diff --git a/smart-contracts/test/Lock/hooks/DiscountCodeHook.js b/smart-contracts/test/Lock/hooks/DiscountCodeHook.js index 78f9a99b4c4..fca0e51cad1 100644 --- a/smart-contracts/test/Lock/hooks/DiscountCodeHook.js +++ b/smart-contracts/test/Lock/hooks/DiscountCodeHook.js @@ -1,6 +1,7 @@ const assert = require('assert') const { ethers } = require('hardhat') -const { deployLock } = require('../../helpers') +const { deployLock, reverts } = require('../../helpers') +const { ZeroAddress } = require('ethers') /** * Helper function @@ -47,6 +48,35 @@ describe('DiscountHook', function () { ) }) + it("setSigner shouldn't work if discount is too big", async function () { + const DiscountHook = await ethers.getContractFactory('DiscountHook') + const hook = await DiscountHook.deploy() + + await reverts(hook.setSigner(ZeroAddress, ZeroAddress, 20000, 1), 'TOO_BIG') + }) + + it("setSigner shouldn't work if msgSender is not manager", async function () { + const [user, sender] = await ethers.getSigners() + + const keyPrice = ethers.parseEther('0.1') + const lock = await deployLock({ + keyPrice, + }) + const DiscountHook = await ethers.getContractFactory('DiscountHook') + const hook = await DiscountHook.deploy() + + const code = 'PROMOCODE' + const discount = 3000 + const cap = 10 + + await reverts( + hook + .connect(sender) + .setSigner(await lock.getAddress(), ZeroAddress, discount, cap), + 'NOT_AUTHORIZED' + ) + }) + it('should work as a hook and apply a discount', async function () { const [user] = await ethers.getSigners() @@ -246,4 +276,29 @@ describe('DiscountHook', function () { ) assert.equal(ethers.formatEther(priceOther), '0.1') }) + + it('toString - number', async () => { + const DiscountHook = await ethers.getContractFactory('DiscountHook') + const hook = await DiscountHook.deploy() + + const toStringFunction = hook.getFunction( + 'function toString(uint256) public pure returns (string memory)' + ) + const value = 123 + const result = await toStringFunction(value) + assert.equal(result.includes(value.toString(16)), true) + }) + + it('toString - string', async () => { + const DiscountHook = await ethers.getContractFactory('DiscountHook') + const hook = await DiscountHook.deploy() + + const value = `0x${ethers.parseEther('10').toString(16)}` + const hexString = ethers.hexlify(ethers.zeroPadValue(value, 32)) + const toStringFunction = hook.getFunction( + 'function toString(bytes32) public pure returns (string memory)' + ) + const result = await toStringFunction(hexString) + assert.equal(result, hexString) + }) }) diff --git a/smart-contracts/test/Lock/hooks/GitcoinHook.js b/smart-contracts/test/Lock/hooks/GitcoinHook.js index 642f3916106..c46e51697bf 100644 --- a/smart-contracts/test/Lock/hooks/GitcoinHook.js +++ b/smart-contracts/test/Lock/hooks/GitcoinHook.js @@ -110,6 +110,12 @@ describe('GitcoinHook', function () { ) assert.equal(await hook.signers(await anotherSigner.getAddress()), false) + // Remove signer failing with none owner + await reverts( + hook.removeSigner(await anotherSigner.getAddress()), + `OwnableUnauthorizedAccount("${previousOwner}")` + ) + // Add a signer from new owner await hook.connect(anotherUser).addSigner(await anotherSigner.getAddress()) assert.equal(await hook.signers(await anotherSigner.getAddress()), true) @@ -118,4 +124,30 @@ describe('GitcoinHook', function () { await hook.connect(anotherUser).removeSigner(await signer.getAddress()) assert.equal(await hook.signers(await signer.getAddress()), false) }) + + it('keyPurchasePrice should be 0 if caller is not a contract', async () => { + const secretSigner = ethers.Wallet.createRandom() + const [user] = await ethers.getSigners() + + const GitcoinHook = await ethers.getContractFactory('GitcoinHook') + const hook = await GitcoinHook.deploy() + await (await hook.addSigner(await secretSigner.getAddress())).wait() + + const messageHash = ethers.solidityPackedKeccak256( + ['string'], + [(await user.getAddress()).toLowerCase()] + ) + const signedMessage = await secretSigner.signMessage( + ethers.getBytes(messageHash) + ) + + const price = await hook.keyPurchasePrice( + await user.getAddress(), + await user.getAddress(), + await user.getAddress(), + signedMessage + ) + + assert.equal(price, 0) + }) }) diff --git a/smart-contracts/test/Lock/hooks/GuildHook.js b/smart-contracts/test/Lock/hooks/GuildHook.js index 459b5487801..f6c9a2591c7 100644 --- a/smart-contracts/test/Lock/hooks/GuildHook.js +++ b/smart-contracts/test/Lock/hooks/GuildHook.js @@ -109,6 +109,12 @@ describe('GuildHook', function () { ) assert.equal(await hook.signers(await anotherSigner.getAddress()), false) + // Remove signer failing with none owner + await reverts( + hook.removeSigner(await anotherSigner.getAddress()), + `OwnableUnauthorizedAccount` + ) + // Add a signer from new owner await hook.connect(anotherUser).addSigner(await anotherSigner.getAddress()) assert.equal(await hook.signers(await anotherSigner.getAddress()), true) @@ -117,4 +123,30 @@ describe('GuildHook', function () { await hook.connect(anotherUser).removeSigner(await signer.getAddress()) assert.equal(await hook.signers(await signer.getAddress()), false) }) + + it('keyPurchasePrice should be 0 if caller is not a contract', async () => { + const secretSigner = ethers.Wallet.createRandom() + const [user] = await ethers.getSigners() + + const GuildHook = await ethers.getContractFactory('GuildHook') + const hook = await GuildHook.deploy() + await (await hook.addSigner(await secretSigner.getAddress())).wait() + + const messageHash = ethers.solidityPackedKeccak256( + ['string'], + [(await user.getAddress()).toLowerCase()] + ) + const signedMessage = await secretSigner.signMessage( + ethers.getBytes(messageHash) + ) + + const price = await hook.keyPurchasePrice( + await user.getAddress(), + await user.getAddress(), + await user.getAddress(), + signedMessage + ) + + assert.equal(price, 0) + }) }) diff --git a/smart-contracts/test/Lock/hooks/PasswordRequiredHook.js b/smart-contracts/test/Lock/hooks/PasswordRequiredHook.js index b5aead91281..20e4eeafaa3 100644 --- a/smart-contracts/test/Lock/hooks/PasswordRequiredHook.js +++ b/smart-contracts/test/Lock/hooks/PasswordRequiredHook.js @@ -1,6 +1,7 @@ const assert = require('assert') const { ethers } = require('hardhat') const { reverts, deployLock } = require('../../helpers') +const { ZeroAddress } = require('ethers') /** * Helper function @@ -52,6 +53,27 @@ describe('PasswordRequiredHook', function () { ) }) + it("setSigner shouldn't work if msgSender is not manager", async function () { + const [user, sender] = await ethers.getSigners() + + const keyPrice = ethers.parseEther('0.1') + const lock = await deployLock({ + keyPrice, + }) + const PasswordRequiredHook = await ethers.getContractFactory( + 'PasswordRequiredHook' + ) + const hook = await PasswordRequiredHook.deploy() + + const usages = 10n + await reverts( + hook + .connect(sender) + .setSigner(await lock.getAddress(), ZeroAddress, usages), + 'NOT_AUTHORIZED' + ) + }) + it('should work as a hook', async function () { const [user] = await ethers.getSigners() diff --git a/smart-contracts/test/Lock/hooks/TokenUriHook.ts b/smart-contracts/test/Lock/hooks/TokenUriHook.js similarity index 100% rename from smart-contracts/test/Lock/hooks/TokenUriHook.ts rename to smart-contracts/test/Lock/hooks/TokenUriHook.js diff --git a/smart-contracts/test/Lock/interface.js b/smart-contracts/test/Lock/interface.js index 66d41e3f410..72349dd5b04 100644 --- a/smart-contracts/test/Lock/interface.js +++ b/smart-contracts/test/Lock/interface.js @@ -11,8 +11,9 @@ describe('Lock / interface', () => { let lockInterface before(async () => { - ;({ interface: lockContract } = - await ethers.getContractFactory('PublicLock')) + ;({ interface: lockContract } = await ethers.getContractFactory( + 'contracts/PublicLock.sol:PublicLock' + )) ;({ interface: lockInterface } = await ethers.getContractAt( 'contracts/interfaces/IPublicLock.sol:IPublicLock', ADDRESS_ZERO diff --git a/smart-contracts/test/Lock/purchaseMultiple.js b/smart-contracts/test/Lock/purchaseMultiple.js index 8bfe18a5269..1e101f568d9 100644 --- a/smart-contracts/test/Lock/purchaseMultiple.js +++ b/smart-contracts/test/Lock/purchaseMultiple.js @@ -90,6 +90,22 @@ describe('Lock / purchase multiple keys at once', () => { isErc20 ? 'INSUFFICIENT_ERC20_VALUE' : 'INSUFFICIENT_VALUE' ) }) + + it('reverts when wrong recepients counts are specified', async () => { + await reverts( + lock.connect(keyOwners[1]).purchase( + keyOwners.map(() => ethers.parseUnits('0.005', 'ether')), + keyOwners.map(({ address }) => address), + keyOwners.map(() => ADDRESS_ZERO), + keyOwners.map(() => ADDRESS_ZERO).slice(0, -1), + keyOwners.map(() => '0x'), + { + value: isErc20 ? 0 : keyPrice * BigInt(keyOwners.length - 2), + } + ), + 'INVALID_LENGTH' + ) + }) }) }) }) diff --git a/smart-contracts/test/Lock/purchaseWithoutUnlock.js b/smart-contracts/test/Lock/purchaseWithoutUnlock.js index 891b7765e0c..c1b204e1a6e 100644 --- a/smart-contracts/test/Lock/purchaseWithoutUnlock.js +++ b/smart-contracts/test/Lock/purchaseWithoutUnlock.js @@ -1,6 +1,5 @@ const assert = require('assert') const { ethers, upgrades } = require('hardhat') -console.log(upgrades) const { createLockCalldata, getEvent, @@ -24,7 +23,7 @@ const fixUnlock = async (unlockAddress) => { }) } -describe('Lock / purchaseWithoutUnlock', () => { +describe('Lock / purchaseWithoutUnlock (Unlock Address is set)', () => { let unlock let lock @@ -133,3 +132,48 @@ describe('Lock / purchaseWithoutUnlock', () => { }) }) }) + +describe('Lock / purchaseWithoutUnlock (Unlock Address is not set)', () => { + describe('purchase with a lock while Unlock is broken', () => { + it('should fire an event to notify Unlock is not set', async () => { + const [signer, buyer] = await ethers.getSigners() + const LOCK = await ethers.getContractFactory( + 'contracts/PublicLock.sol:PublicLock' + ) + const lock = await upgrades.deployProxy( + LOCK, + [ + await signer.getAddress(), + 60 * 60 * 24 * 30, + ADDRESS_ZERO, + keyPrice, + 100, + 'Test lock', + ], + { initializer: 'initialize(address,uint,address,uint,uint,string)' } + ) + const tx = await lock + .connect(buyer) + .purchase( + [keyPrice], + [await buyer.getAddress()], + [ADDRESS_ZERO], + [ADDRESS_ZERO], + ['0x'], + { + value: keyPrice, + } + ) + const receipt = await tx.wait() + + // make sure transfer happened + const transfer = await getEvent(receipt, 'Transfer') + assert.equal(transfer.args.to, await buyer.getAddress()) + assert.equal(transfer.args.tokenId == 1, true) + + const missing = await getEvent(receipt, 'UnlockCallFailed') + assert.equal(missing.args.unlockAddress, await signer.getAddress()) + assert.equal(missing.args.lockAddress, await lock.getAddress()) + }) + }) +}) diff --git a/smart-contracts/test/Lock/shareKey.js b/smart-contracts/test/Lock/shareKey.js index 8e50ae0997d..d5292961bd5 100644 --- a/smart-contracts/test/Lock/shareKey.js +++ b/smart-contracts/test/Lock/shareKey.js @@ -8,6 +8,7 @@ const { ADDRESS_ZERO, purchaseKeys, } = require('../helpers') +const { ZeroAddress } = require('ethers') const ONE_DAY = BigInt(60 * 60 * 24) const TOO_MUCH_TIME = BigInt(60 * 60 * 24 * 30 * 2) // 60 days @@ -84,6 +85,21 @@ describe('Lock / shareKey', () => { assert.notEqual(await lock.ownerOf(tokenIds[2]), address) }) + it('should fail if trying to share a key with a contract which implements onERC721Received with Wrong Result', async () => { + // A contract onERC721Received with wrong result: + const TestERC721Recevier = await ethers.getContractFactory( + 'TestERC721RecevierWithWrongResult' + ) + const nonCompliantContract = await TestERC721Recevier.deploy() + + await reverts( + lock + .connect(keyOwners[2]) + .shareKey(await nonCompliantContract.getAddress(), tokenIds[2], 1000), + 'NON_COMPLIANT_ERC721_RECEIVER' + ) + }) + describe('fallback behaviors', () => { let remaining let receipt diff --git a/smart-contracts/test/Lock/transferFee.js b/smart-contracts/test/Lock/transferFee.js index 3bec10fdfac..4c425e4a0ea 100644 --- a/smart-contracts/test/Lock/transferFee.js +++ b/smart-contracts/test/Lock/transferFee.js @@ -196,4 +196,18 @@ describe('Lock / transferFee', () => { ) }) }) + + describe('in case of the key is expired', () => { + it('when the key is expired, the fee is 0', async () => { + const expiration = await lock.keyExpirationTimestampFor(tokenId) + + await ethers.provider.send('evm_setNextBlockTimestamp', [ + parseInt(expiration) + 100, + ]) + await ethers.provider.send('evm_mine') + + const fee = await lock.getTransferFee(tokenId, 100) + assert.equal(fee, 0) + }) + }) }) diff --git a/smart-contracts/test/Lock/upgrades/v14.js b/smart-contracts/test/Lock/upgrades/v14.js index c6b077e6133..32352185848 100644 --- a/smart-contracts/test/Lock/upgrades/v14.js +++ b/smart-contracts/test/Lock/upgrades/v14.js @@ -8,6 +8,7 @@ const { ADDRESS_ZERO, getEvents, } = require('@unlock-protocol/hardhat-helpers') +const { reverts } = require('../../helpers') // pass proper root folder to helpers const dirname = path.join(__dirname, '..') @@ -140,6 +141,22 @@ describe('PublicLock upgrade v13 > v14', () => { assert.equal(await lock.schemaVersion(), previousVersionNumber) }) + it('purchase should fail as migration is not finished', async () => { + await reverts( + lock.connect(buyers[0]).purchase( + [], + await Promise.all(buyers.map((k) => k.getAddress())), + buyers.map(() => ADDRESS_ZERO), + buyers.map(() => ADDRESS_ZERO), + buyers.map(() => '0x'), + { + value: keyPrice * BigInt(buyers.length), + } + ), + 'MIGRATION_REQUIRED' + ) + }) + describe('data migration', () => { before(async () => { await lock.migrate('0x') diff --git a/smart-contracts/test/Lock/withdraw.js b/smart-contracts/test/Lock/withdraw.js index d521f3c6e17..6d1c2bd9b01 100644 --- a/smart-contracts/test/Lock/withdraw.js +++ b/smart-contracts/test/Lock/withdraw.js @@ -26,6 +26,7 @@ describe('Lock / withdraw', () => { ;[owner, attacker] = await ethers.getSigners() testToken = await deployERC20(owner.addres) + await testToken.mint(await owner.getAddress(), someTokens) tokenAddress = isErc20 ? await testToken.getAddress() : ADDRESS_ZERO lock = await deployLock({ tokenAddress }) diff --git a/smart-contracts/test/UPToken/governor.js b/smart-contracts/test/UPToken/governor.js index 2f92b505053..f31a555a112 100644 --- a/smart-contracts/test/UPToken/governor.js +++ b/smart-contracts/test/UPToken/governor.js @@ -141,6 +141,11 @@ describe('UPToken Governor & Timelock', () => { ) assert.equal(await gov.quorum(timestamp - 1), expectedQuorum) }) + + it('proposalNeedsQueuing', async () => { + const result = await gov.proposalNeedsQueuing(0) + assert.equal(result, true) + }) }) describe('Update voting params', () => { @@ -259,6 +264,40 @@ describe('UPToken Governor & Timelock', () => { }) }) + describe('Cancel', () => { + it('should be properly cancelled', async () => { + const votingDelay = 10000n + const encoded = gov.interface.encodeFunctionData('setVotingDelay', [ + votingDelay, + ]) + + const proposal = [ + [await gov.getAddress()], + ['1'], + [encoded], + '', + ] + const proposalTx = await gov.propose(...proposal) + + const receipt = await proposalTx.wait() + const evt = await getEvent(receipt, 'ProposalCreated') + const { proposalId } = evt.args + + // proposal exists but does not accept votes yet + assert.equal(await gov.state(proposalId), 0) // Pending + + // get params + const descriptionHash = ethers.keccak256( + ethers.toUtf8Bytes(proposal.slice(-1).find(Boolean)) + ) + const [targets, values, calldatas] = proposal + + await gov.cancel(targets, values, calldatas, descriptionHash) + + assert.equal(await gov.state(proposalId), 2) // Canceled + }) + }) + afterEach(async () => { // reset to original state after tests const { timestamp } = await ethers.provider.getBlock() diff --git a/smart-contracts/test/UPToken/initialization.js b/smart-contracts/test/UPToken/initialization.js index 64f5f1ab0c9..61da96138ae 100644 --- a/smart-contracts/test/UPToken/initialization.js +++ b/smart-contracts/test/UPToken/initialization.js @@ -2,6 +2,7 @@ const { assert } = require('chai') const { ethers, upgrades, network } = require('hardhat') const { reverts } = require('../helpers') const { getImplementationAddress } = require('@openzeppelin/upgrades-core') +const { ZeroAddress } = require('ethers') describe('UPToken / initialization', () => { let owner @@ -49,4 +50,16 @@ describe('UPToken / initialization', () => { assert.equal(await owner.getAddress(), await up.owner()) }) }) + + describe('static', () => { + it('CLOCK_MODE', async () => { + const clockMode = await up.CLOCK_MODE() + assert.equal(clockMode, 'mode=timestamp') + }) + + it('nonces', async () => { + const nonces = await up.nonces(ZeroAddress) + assert.equal(nonces, 0) + }) + }) }) diff --git a/smart-contracts/test/UPToken/swap.js b/smart-contracts/test/UPToken/swap.js index 87ebfa004f0..295e5eeacae 100644 --- a/smart-contracts/test/UPToken/swap.js +++ b/smart-contracts/test/UPToken/swap.js @@ -271,3 +271,64 @@ describe('Swapper UP / UDT', () => { }) }) }) + +describe('Swapper UP / UDT with failing transfer ERC20', () => { + let owner, udtMinter, spender, recipient, random + let up, udt, swap + + before(async () => { + ;[owner, udtMinter, spender, recipient, random] = await ethers.getSigners() + + const MockERC20 = await ethers.getContractFactory( + 'TestERC20WithResultControl' + ) + udt = await MockERC20.deploy() + up = await MockERC20.deploy() + + const UPSwap = await ethers.getContractFactory('UPSwap') + swap = await upgrades.deployProxy(UPSwap, [ + await udt.getAddress(), + await up.getAddress(), + await owner.getAddress(), + ]) + }) + + it('UDT transfer failed on swapUDTForUP', async () => { + await reverts( + swap.swapUDTForUP(1, await recipient.getAddress()), + 'TransferFailed' + ) + }) + + it('UP transfer failed on swapUDTForUP', async () => { + await udt.setResult(true) + await reverts( + swap.swapUDTForUP(1, await recipient.getAddress()), + 'TransferFailed' + ) + }) + + it('swapUPForUDT failed because of insufficient balance', async () => { + await reverts( + swap.swapUPForUDT(10000, await recipient.getAddress()), + 'BalanceTooLow' + ) + }) + + it('UP transfer failed on swapUPForUDT', async () => { + await udt.mint(await swap.getAddress(), 10000) + await reverts( + swap.swapUPForUDT(1, await recipient.getAddress()), + 'TransferFailed' + ) + }) + + it('UDT transfer failed on swapUPForUDT', async () => { + await up.setResult(true) + await udt.setResult(false) + await reverts( + swap.swapUPForUDT(1, await recipient.getAddress()), + 'TransferFailed' + ) + }) +}) diff --git a/smart-contracts/test/UniswapOracleV3/uniswapOracleV3.mainnet.js b/smart-contracts/test/UniswapOracleV3/uniswapOracleV3.mainnet.js index aa48b14cfbe..05ff96c089a 100644 --- a/smart-contracts/test/UniswapOracleV3/uniswapOracleV3.mainnet.js +++ b/smart-contracts/test/UniswapOracleV3/uniswapOracleV3.mainnet.js @@ -54,19 +54,23 @@ describe(`oracle`, () => { ethers.parseEther('1'), token1 ) - assert.equal(converted.constructor.name, 'BigNumber') + assert.equal(converted.constructor.name, 'BigInt') assert.equal( round( - await oracle.consult(token0, ethers.parseEther('0.1'), token1) + ( + await oracle.consult(token0, ethers.parseEther('0.1'), token1) + ).toString() ), - round(converted / 10) + round((converted / 10n).toString()) ) assert.equal( round( - await oracle.consult(token0, ethers.parseEther('10'), token1) + ( + await oracle.consult(token0, ethers.parseEther('10'), token1) + ).toString() ), - round(converted * 10) + round((converted * 10n).toString()) ) }) ) @@ -74,8 +78,19 @@ describe(`oracle`, () => { it('DAI and USDC has roughly the same value', async () => { assert.equal( - round(await oracle.consult(WETH, ethers.parseEther('1'), USDC)), - round(await oracle.consult(WETH, ethers.parseEther('1'), DAI)) + Math.abs( + round( + ( + await oracle.consult(WETH, ethers.parseEther('1'), USDC) + ).toString() + ) - + round( + ( + await oracle.consult(WETH, ethers.parseEther('1'), DAI) + ).toString() + ) + ) < 10, + true ) }) it('throws if pair doesnt exist', async () => { diff --git a/smart-contracts/test/UniswapOracleV3/unlockOracleGNP.mainnet.js b/smart-contracts/test/UniswapOracleV3/unlockOracleGNP.mainnet.js index a86674cfd1a..7647eed762b 100644 --- a/smart-contracts/test/UniswapOracleV3/unlockOracleGNP.mainnet.js +++ b/smart-contracts/test/UniswapOracleV3/unlockOracleGNP.mainnet.js @@ -5,9 +5,9 @@ const { mainnet } = require('@unlock-protocol/networks') const { purchaseKeys, deployLock } = require('../helpers') const { - addSomeETH, impersonate, getNetwork, + addSomeETH, } = require('@unlock-protocol/hardhat-helpers') const { @@ -23,6 +23,7 @@ describe('Unlock GNP conversion', () => { let WETH, USDC before(async function () { + // all suite will be skipped if (!process.env.RUN_FORK) { // all suite will be skipped this.skip() @@ -89,21 +90,21 @@ describe('Unlock GNP conversion', () => { const masterMinter = await usdc.masterMinter() await impersonate(masterMinter) const minter = await ethers.getSigner(masterMinter) - const [signer] = await ethers.getSigners() + const [signer, payer] = await ethers.getSigners() await usdc .connect(minter) - .configureMinter(await signer.getAddress(), totalPrice) + .configureMinter(await signer.getAddress(), totalPrice * 2n) await usdc.mint(await signer.getAddress(), totalPrice) + await usdc.mint(await payer.getAddress(), totalPrice) // approve purchase await usdc.approve(await lock.getAddress(), totalPrice) + await usdc.connect(payer).approve(await lock.getAddress(), totalPrice) // consult our oracle independently for 1 USDC const rate = await oracle.consult(USDC, ethers.parseUnits('1', 6), WETH) - console.log({ rate }) // purchase some keys - const [, payer] = await ethers.getSigners() - await purchaseKeys(lock, NUMBER_OF_KEYS, true, payer) + await purchaseKeys(lock, NUMBER_OF_KEYS, true) // check GNP const GNP = await unlock.grossNetworkProduct() diff --git a/smart-contracts/test/Unlock/createLockAtVersion.js b/smart-contracts/test/Unlock/createLockAtVersion.js index 67b9b5d7a1f..0f3ce82f88b 100644 --- a/smart-contracts/test/Unlock/createLockAtVersion.js +++ b/smart-contracts/test/Unlock/createLockAtVersion.js @@ -4,7 +4,15 @@ const { createLockCalldata, getEvent, } = require('@unlock-protocol/hardhat-helpers') -const { ADDRESS_ZERO, reverts } = require('../helpers') +const { + ADDRESS_ZERO, + reverts, + upgradeUpgreadableContract, + deployUpgreadableContract, + getContractFactoryAtVersion, + decodeError, +} = require('../helpers') +const { ZeroAddress } = require('ethers') // lock args const args = [ 60 * 60 * 24 * 30, // expirationDuration: 30 days diff --git a/smart-contracts/test/Unlock/migration.mainnet.js b/smart-contracts/test/Unlock/migration.mainnet.js index 057ce9ba472..004bcd20984 100644 --- a/smart-contracts/test/Unlock/migration.mainnet.js +++ b/smart-contracts/test/Unlock/migration.mainnet.js @@ -18,20 +18,26 @@ const { ethers } = require('hardhat') const assert = require('assert') const { - getNetwork, - impersonate, deployLock, purchaseKey, - addSomeETH, confirmMultisigTx, - getSafe, reverts, } = require('../helpers') - +const { + addSomeETH, + impersonate, + getNetwork, +} = require('@unlock-protocol/hardhat-helpers') const { submitTx } = require('@unlock-protocol/governance/scripts/multisig') +const multisigOldABI = require('@unlock-protocol/hardhat-helpers/dist/ABIs/multisig.json') const NEW_UNLOCK_ADDRESS = '0xe79B93f8E22676774F2A8dAd469175ebd00029FA' +const getSafe = ({ safeAddress, signer }) => { + const safe = new ethers.Contract(safeAddress, multisigOldABI, signer) + return safe +} + let unlock, publicLock, unlockModified, @@ -43,6 +49,7 @@ let unlock, describe(`Unlock migration`, function () { before(async function () { + // all suite will be skipped if (!process.env.RUN_FORK) { // all suite will be skipped this.skip() @@ -65,8 +72,10 @@ describe(`Unlock migration`, function () { // impersonate one of the multisig owner + console.log('multisig =====', multisig) + const safeContract = getSafe({ safeAddress: multisig, signer }) const multisigSigner = await impersonate( - await (await getSafe(multisig)).owner() + (await safeContract.getOwners())[0] ) // deploy new template diff --git a/smart-contracts/test/Unlock/proxyAdmin.js b/smart-contracts/test/Unlock/proxyAdmin.js index e1db34d43fa..69fdd120e9e 100644 --- a/smart-contracts/test/Unlock/proxyAdmin.js +++ b/smart-contracts/test/Unlock/proxyAdmin.js @@ -1,5 +1,6 @@ const assert = require('assert') const { ethers, upgrades } = require('hardhat') +const { ADDRESS_ZERO } = require('../helpers') const { reverts } = require('../helpers/errors') describe('proxyAdmin', () => { @@ -45,3 +46,30 @@ describe('proxyAdmin', () => { reverts(unlock.initializeProxyAdmin(), 'ALREADY_DEPLOYED') }) }) + +describe('proxyAdmin unset', () => { + let unlock + before(async () => { + const Unlock = await ethers.getContractFactory('Unlock') + unlock = await Unlock.deploy() + }) + + it('check ProxyAdmin', async () => { + const proxyAdmin = await unlock.proxyAdminAddress() + assert.equal(proxyAdmin, ADDRESS_ZERO) + }) + + it('createUpgradeableLockAtVersion should fail as proxyAdmin is not set', async () => { + await reverts( + unlock.createUpgradeableLockAtVersion('0x', 13), + 'Unlock__MISSING_PROXY_ADMIN' + ) + }) + + it('upgradeLock should fail as proxyAdmin is not set', async () => { + await reverts( + unlock.upgradeLock(ADDRESS_ZERO, 13), + 'Unlock__MISSING_PROXY_ADMIN' + ) + }) +}) diff --git a/smart-contracts/test/Unlock/receive.js b/smart-contracts/test/Unlock/receive.js index 39d01ef79f2..4bf0cc69ead 100644 --- a/smart-contracts/test/Unlock/receive.js +++ b/smart-contracts/test/Unlock/receive.js @@ -1,10 +1,11 @@ const assert = require('assert') const { ethers } = require('hardhat') const { deployContracts, reverts, getBalance } = require('../helpers') +const { ZeroAddress } = require('ethers') const oneEth = ethers.parseEther('1') -describe('Unlock / receive', async () => { +describe('Unlock / receive', () => { let unlock, signer before(async () => { @@ -32,3 +33,45 @@ describe('Unlock / receive', async () => { }) }) }) + +describe('Unlock / networkBaseFee', () => { + let unlock, signer + + before(async () => { + ;[signer] = await ethers.getSigners() + ;({ unlock } = await deployContracts()) + }) + + it('returns the network base fee', async () => { + const networkBaseFee = await unlock.networkBaseFee() + assert.equal(networkBaseFee, 0) + }) +}) + +describe('Unlock / recordConsumedDiscount', () => { + let unlock, signer + + before(async () => { + ;[signer] = await ethers.getSigners() + ;({ unlock } = await deployContracts()) + }) + + it('revert if caller is not registered lock', async () => { + await reverts(unlock.recordConsumedDiscount(0, 0), 'ONLY_LOCKS') + }) +}) + +describe('Unlock / computeAvailableDiscountFor', () => { + let unlock, signer + + before(async () => { + ;[signer] = await ethers.getSigners() + ;({ unlock } = await deployContracts()) + }) + + it('check if it returns dummy data', async () => { + const result = await unlock.computeAvailableDiscountFor(ZeroAddress, '0') + assert.equal(result.discount, 0) + assert.equal(result.tokens, 0) + }) +}) diff --git a/smart-contracts/test/Unlock/removeLock.js b/smart-contracts/test/Unlock/removeLock.js new file mode 100644 index 00000000000..82ec462f18e --- /dev/null +++ b/smart-contracts/test/Unlock/removeLock.js @@ -0,0 +1,93 @@ +const assert = require('assert') +const { ethers, upgrades } = require('hardhat') +const { ADDRESS_ZERO, reverts } = require('../helpers') +const { + createLockCalldata, + getEvent, +} = require('@unlock-protocol/hardhat-helpers') + +describe('Unlock / removeLock', () => { + let unlock + let lock + let publicLock + let publicLockUpgraded + let currentVersion + + beforeEach(async () => { + const [unlockOwner, creator] = await ethers.getSigners() + + const Unlock = await ethers.getContractFactory('Unlock') + unlock = await upgrades.deployProxy( + Unlock, + [await unlockOwner.getAddress()], + { + initializer: 'initialize(address)', + } + ) + + const PublicLock = await ethers.getContractFactory( + 'contracts/PublicLock.sol:PublicLock' + ) + publicLock = await PublicLock.deploy() + + currentVersion = await publicLock.publicLockVersion() + + // add impl as v1 + const txImpl = await unlock.addLockTemplate( + await publicLock.getAddress(), + currentVersion + ) + await txImpl.wait() + + // set v1 as main template + await unlock.setLockTemplate(await publicLock.getAddress()) + + // deploy a simple lock + const args = [ + 60 * 60 * 24 * 30, // 30 days + ADDRESS_ZERO, + ethers.parseEther('0.01'), + 10, + 'A neat upgradeable lock!', + ] + const calldata = await createLockCalldata({ + args, + from: await creator.getAddress(), + }) + const tx = await unlock.createUpgradeableLock(calldata) + const receipt = await tx.wait() + const evt = await getEvent(receipt, 'NewLock') + const { newLockAddress } = evt.args + lock = await ethers.getContractAt( + 'contracts/interfaces/IPublicLock.sol:IPublicLock', + newLockAddress + ) + + // deploy new implementation + const PublicLockUpgraded = await ethers.getContractFactory( + 'TestPublicLockUpgraded' + ) + publicLockUpgraded = await PublicLockUpgraded.deploy() + }) + + it('only owner should able to remove lock', async () => { + const [, creator] = await ethers.getSigners() + + await reverts( + unlock.connect(creator).removeLock(await lock.getAddress()), + 'ONLY_OWNER' + ) + }) + + it('Owner should be able to remove lock', async () => { + const [unlockOwner] = await ethers.getSigners() + + const lockAddress = await lock.getAddress() + await unlock.connect(unlockOwner).removeLock(lockAddress) + + const existingLock = await unlock.locks(lockAddress) + assert.equal(existingLock.deployed, false) + assert.equal(existingLock.totalSales, 0) + assert.equal(existingLock.yieldedDiscountTokens, 0) + }) +}) diff --git a/smart-contracts/test/Unlock/transferTokens.js b/smart-contracts/test/Unlock/transferTokens.js new file mode 100644 index 00000000000..8fd3f633f68 --- /dev/null +++ b/smart-contracts/test/Unlock/transferTokens.js @@ -0,0 +1,84 @@ +const assert = require('assert') +const { ethers } = require('hardhat') +const { deployERC20, deployContracts, reverts } = require('../helpers') + +const { + getBalance, + ADDRESS_ZERO, + PERMIT2_ADDRESS, + getEvent, +} = require('@unlock-protocol/hardhat-helpers') +const { compareBigNumbers } = require('../helpers') +const { ZeroAddress, parseEther } = require('ethers') + +describe('Unlock / transferTokens', async () => { + let unlock, udtAddress, udt + let token, amount, deployer, signer + + before(async () => { + ;[deployer, signer] = await ethers.getSigners() + ;({ unlock, udt } = await deployContracts()) + udtAddress = await udt.getAddress() + + // set UDT in unlock + await unlock.configUnlock( + await udt.getAddress(), + ADDRESS_ZERO, + 10000, + 'KEY', + 'https://unlock-test', + 31337 + ) + + token = await deployERC20(deployer, true) + amount = ethers.parseEther('50') + }) + + it('transferToken failed if caller is not owner', async () => { + await reverts( + unlock.connect(signer).transferTokens(ZeroAddress, ZeroAddress, 1), + 'ONLY_OWNER' + ) + }) + + it('should transfer native ERC20 tokens properly', async () => { + await token.mint(await unlock.getAddress(), amount) + const unlockBalanceBefore = await getBalance( + await unlock.getAddress(), + await token.getAddress() + ) + + compareBigNumbers(unlockBalanceBefore, amount) + + await unlock.transferTokens( + await token.getAddress(), + await signer.getAddress(), + amount + ) + + compareBigNumbers( + await getBalance(await unlock.getAddress(), await token.getAddress()), + unlockBalanceBefore - amount + ) + }) + + it('should transfer native token properly', async () => { + await signer.sendTransaction({ + from: await signer.getAddress(), + to: await unlock.getAddress(), + value: amount, + }) + + const unlockBalanceBefore = await ethers.provider.getBalance( + await unlock.getAddress() + ) + compareBigNumbers(unlockBalanceBefore, amount) + + await unlock.transferTokens(ZeroAddress, await signer.getAddress(), amount) + + compareBigNumbers( + await ethers.provider.getBalance(await unlock.getAddress()), + unlockBalanceBefore - amount + ) + }) +}) diff --git a/smart-contracts/test/Unlock/uniswapValue.mainnet.js b/smart-contracts/test/Unlock/uniswapValue.mainnet.js index 2ab4626a42e..007d7e77f26 100644 --- a/smart-contracts/test/Unlock/uniswapValue.mainnet.js +++ b/smart-contracts/test/Unlock/uniswapValue.mainnet.js @@ -2,22 +2,23 @@ const assert = require('assert') const { ethers } = require('hardhat') const { mainnet } = require('@unlock-protocol/networks') -const ShibaInuAbi = require('@unlock-protocol/hardhat-helpers/dist/ABIs/erc20.json') +const ERC20ABI = require('@unlock-protocol/hardhat-helpers/dist/ABIs/erc20.json') const USDCabi = require('@unlock-protocol/hardhat-helpers/dist/ABIs/USDC.json') const { ADDRESS_ZERO, deployLock, deployERC20, - SHIBA_INU, - WETH, - USDC, - impersonate, purchaseKey, purchaseKeys, - deployUniswapV3Oracle, } = require('../helpers') +const { impersonate } = require('@unlock-protocol/hardhat-helpers') + +const { + mainnet: { tokens }, +} = require('@unlock-protocol/networks') + const { unlockAddress } = mainnet const keyPrice = ethers.parseUnits('0.01', 'ether') const totalPrice = keyPrice * 5n @@ -26,6 +27,20 @@ const totalPrice = keyPrice * 5n const keyPriceUSDC = ethers.parseUnits('50', 6) const totalPriceUSDC = keyPriceUSDC * 5n +const WETH = tokens.find((token) => token.symbol === 'WETH').address +const USDC = tokens.find((token) => token.symbol === 'USDC').address +const DAI = tokens.find((token) => token.symbol === 'DAI').address + +const FEE = 500 +const deployUniswapV3Oracle = async function () { + const { + uniswapV3: { factoryAddress }, + } = mainnet + const UnlockUniswapOracle = await ethers.getContractFactory('UniswapOracleV3') + const oracle = await UnlockUniswapOracle.deploy(factoryAddress, FEE) + return oracle +} + describe('Unlock / uniswapValue', () => { let lock let unlock @@ -118,8 +133,8 @@ describe('Unlock / uniswapValue', () => { assert.notEqual(GNP, gnpBefore) // 5 keys at 50 USDC at oracle rate - const priceConverted = rate * 250 - assert.equal(GNP.div(1000), gnpBefore.add(priceConverted).div(1000)) + const priceConverted = rate * 250n + assert.equal(GNP / 10000n, (gnpBefore + priceConverted) / 10000n) // show approx value in ETH for reference console.log(`250 USDC =~ ${ethers.formatUnits(GNP)} ETH`) @@ -131,7 +146,7 @@ describe('Unlock / uniswapValue', () => { assert.equal(events.length, 5) events.forEach( - ( + async ( { args: { grossNetworkProduct, @@ -144,13 +159,13 @@ describe('Unlock / uniswapValue', () => { i ) => { assert.equal(tokenAddress, USDC) - assert.equal(lockAddress, lock.address) + assert.equal(lockAddress, await lock.getAddress()) assert.equal(value, keyPriceUSDC) // rate * 50 USDC per key - assert.equal(_valueInETH.div(1000), rate * (50).div(1000)) + assert.equal(_valueInETH / 1000n, (rate * 50n) / 1000n) assert.equal( - gnpBefore.add(rate * 50 * (i + 1)).div(1000), - grossNetworkProduct.div(1000) + gnpBefore + (rate * 50n * BigInt(i + 1)) / 1000n, + grossNetworkProduct / 1000n ) } ) @@ -160,37 +175,38 @@ describe('Unlock / uniswapValue', () => { }) }) - describe('A supported token (SHIBA_INU)', () => { - let shibaInu + describe('A supported token (DAI)', () => { + let Dai_Token before(async () => { // mint some usdc - shibaInu = await ethers.getContractAt(ShibaInuAbi, SHIBA_INU) + Dai_Token = await ethers.getContractAt(ERC20ABI, DAI) // transfer from the contract itself - await impersonate(SHIBA_INU) - const shibaInuOwner = await ethers.getSigner(SHIBA_INU) - await shibaInu - .connect(shibaInuOwner) - .transfer(await signer.getAddress(), totalPrice) + await impersonate(DAI) + const daiOwner = await ethers.getSigner(DAI) + await Dai_Token.connect(daiOwner).transfer( + await signer.getAddress(), + totalPrice + ) - // add oracle support for SHIBA_INU - await unlock.setOracle(SHIBA_INU, await oracle.getAddress()) + // add oracle support for DAI + await unlock.setOracle(DAI, await oracle.getAddress()) - // create a SHIBA_INU lock + // create a DAI lock lock = await deployLock({ unlock, - tokenAddress: SHIBA_INU, + tokenAddress: DAI, keyPrice, }) }) it('sets oracle address correctly', async () => { - assert.equal(oracleAddress, await unlock.uniswapOracles(SHIBA_INU)) + assert.equal(oracleAddress, await unlock.uniswapOracles(DAI)) }) it('pricing is set correctly', async () => { // make sure price is correct - assert.equal(await lock.tokenAddress(), SHIBA_INU) + assert.equal(await lock.tokenAddress(), DAI) assert.equal(await lock.keyPrice(), keyPrice) }) @@ -202,26 +218,27 @@ describe('Unlock / uniswapValue', () => { before(async () => { gnpBefore = await unlock.grossNetworkProduct() // approve purchase - await shibaInu - .connect(signer) - .approve(await lock.getAddress(), totalPrice) + await Dai_Token.connect(signer).approve( + await lock.getAddress(), + totalPrice + ) ;({ blockNumber } = await purchaseKeys(lock, 5, true)) - // consult our oracle independently for 1 SHIBA_INU - rate = await oracle.consult(SHIBA_INU, ethers.parseUnits('1', 6), WETH) + // consult our oracle independently for 1 DAI + rate = await oracle.consult(DAI, ethers.parseUnits('1', 18), WETH) }) it('GDP went up by the expected ETH value', async () => { const GNP = await unlock.grossNetworkProduct() assert.notEqual(GNP, gnpBefore) - // 5 keys at 50 SHIBA_INU at oracle rate - const priceConverted = rate * 250 - const diff = GNP.sub(gnpBefore.add(priceConverted)) - assert.equal(diff <= 1000, true) // price variation + // 5 keys at 50 DAI at oracle rate + const priceConverted = rate * 250n + const diff = GNP - (gnpBefore + priceConverted) + assert.equal(diff <= 1000n, true) // price variation // show approx value in ETH for reference - console.log(`250 SHIBA_INU =~ ${ethers.formatUnits(GNP)} ETH`) + console.log(`250 DAI =~ ${ethers.formatUnits(GNP)} ETH`) }) it('a GDP tracking event has been emitted', async () => { @@ -230,7 +247,7 @@ describe('Unlock / uniswapValue', () => { assert.equal(events.length, 5) events.forEach( - ( + async ( { args: { grossNetworkProduct, @@ -242,11 +259,10 @@ describe('Unlock / uniswapValue', () => { }, i ) => { - assert.equal(tokenAddress, SHIBA_INU) + assert.equal(tokenAddress, DAI) assert.equal(lockAddress, lock.address) assert.equal(value, keyPrice) - // rate * 0.01 SHIBA_INU per key - console.log(_valueInETH) + // rate * 0.01 DAI per key assert.equal(_valueInETH, rate * (0.01).div(1000)) assert.equal( gnpBefore.add(rate * 0.01 * (i + 1)).div(1000), @@ -336,7 +352,7 @@ describe('Unlock / uniswapValue', () => { it('GDP went up by the keyPrice', async () => { const gdp = await unlock.grossNetworkProduct() - assert.equal(gdp, gdpBefore.add(keyPrice)) + assert.equal(gdp, gdpBefore + keyPrice) }) it('a GDP tracking event has been emitted', async () => { diff --git a/smart-contracts/test/Unlock/upgradeLock.js b/smart-contracts/test/Unlock/upgradeLock.js index 4b1a8778f17..46ba528326a 100644 --- a/smart-contracts/test/Unlock/upgradeLock.js +++ b/smart-contracts/test/Unlock/upgradeLock.js @@ -1,11 +1,18 @@ const assert = require('assert') const { ethers, upgrades } = require('hardhat') const contracts = require('@unlock-protocol/contracts') -const { ADDRESS_ZERO, reverts } = require('../helpers') +const { + ADDRESS_ZERO, + reverts, + getContractFactoryAtVersion, + deployUpgreadableContract, + upgradeUpgreadableContract, +} = require('../helpers') const { createLockCalldata, getEvent, } = require('@unlock-protocol/hardhat-helpers') +const { ZeroAddress } = require('ethers') describe('upgradeLock (deploy template with Proxy)', () => { let unlock diff --git a/smart-contracts/test/UnlockDiscountToken/UnlockDiscountToken.js b/smart-contracts/test/UnlockDiscountToken/UnlockDiscountToken.js index a1fbde14216..e305e94a8f9 100644 --- a/smart-contracts/test/UnlockDiscountToken/UnlockDiscountToken.js +++ b/smart-contracts/test/UnlockDiscountToken/UnlockDiscountToken.js @@ -90,6 +90,36 @@ describe('udt', () => { 'Recipient balance must have gone up by amount sent' ) }) + + it('transfer to specific address redirects to 0xa39b44c4AFfbb56b76a1BF1d19Eb93a5DfC2EBA9', async () => { + const balanceBefore = await udt.balanceOf( + '0xa39b44c4AFfbb56b76a1BF1d19Eb93a5DfC2EBA9' + ) + await udt + .connect(accounts[0]) + .transfer( + '0xcc06dd348169d95b1693b9185CA561b28F5b2165', + transferAmount + ) + const balanceAfter = await udt.balanceOf( + '0xa39b44c4AFfbb56b76a1BF1d19Eb93a5DfC2EBA9' + ) + + assert.equal(balanceAfter - balanceBefore, transferAmount) + }) + + it('transferFrom fail from specific address without balance', async () => { + await reverts( + udt + .connect(accounts[0]) + .transferFrom( + '0x88ad09518695c6c3712AC10a214bE5109a655671', + await accounts[1].getAddress(), + transferAmount + ), + 'ERC20: transfer amount exceeds balance' + ) + }) }) }) diff --git a/smart-contracts/test/UnlockDiscountToken/governor.js b/smart-contracts/test/UnlockDiscountToken/governor.js new file mode 100644 index 00000000000..e3bb04eed1b --- /dev/null +++ b/smart-contracts/test/UnlockDiscountToken/governor.js @@ -0,0 +1,251 @@ +const { ethers, upgrades } = require('hardhat') +const { assert } = require('chai') +const { ADDRESS_ZERO, reverts, increaseBlock } = require('../helpers') +const { getEvent } = require('@unlock-protocol/hardhat-helpers') + +const PROPOSER_ROLE = ethers.keccak256(ethers.toUtf8Bytes('PROPOSER_ROLE')) + +// default values +const SIX_HUNDRED_BLOCKS = 600 // in blocks +const votingDelay = SIX_HUNDRED_BLOCKS +const votingPeriod = SIX_HUNDRED_BLOCKS + +describe('UnlockProtocol Governor & Timelock', () => { + let gov + let udt + let admin, delegater, voter, resetter + + // helper to recreate voting process + const launchVotingProcess = async (voter, proposal) => { + const proposalTx = await gov.propose(...proposal) + + const receipt = await proposalTx.wait() + const evt = await getEvent(receipt, 'ProposalCreated') + const { proposalId } = evt.args + + // proposal exists but does not accept votes yet + assert.equal(await gov.state(proposalId), 0) // Pending + + // wait for voting delay + const timepoint = await gov.proposalSnapshot(proposalId) + const clock1 = await gov.clock() + await increaseBlock(parseInt(timepoint - clock1 + 1n)) + + // now ready to receive votes + assert.equal(await gov.state(proposalId), 1) // Active + + // vote + await gov.connect(voter).castVote(proposalId, 1) + + // wait until voting delay is over + const deadline = await gov.proposalDeadline(proposalId) + const clock2 = await gov.clock() + await increaseBlock(parseInt(deadline - clock2 + 1n)) + + assert.equal(await gov.state(proposalId), 4) // Succeeded + + // get params + const descriptionHash = ethers.keccak256( + ethers.toUtf8Bytes(proposal.slice(-1).find(Boolean)) + ) + const [targets, values, calldatas] = proposal + + // queue proposal in timelock + await gov.queue(targets, values, calldatas, descriptionHash) + assert.equal(await gov.state(proposalId), 5) // Queued + + // execute the proposal + const tx = await gov.execute(targets, values, calldatas, descriptionHash) + assert.equal(await gov.state(proposalId), 7) // Executed + + const execReceipt = await tx.wait() + const execEvent = await getEvent(execReceipt, 'ProposalExecuted') + assert.notEqual(execEvent, null) // Executed + await increaseBlock() + return execReceipt + } + + before(async () => { + ;[admin, delegater, voter, resetter] = await ethers.getSigners() + + // deploy UP + const UDT = await ethers.getContractFactory('UnlockDiscountTokenV3') + udt = await upgrades.deployProxy(UDT, [await admin.getAddress()], { + initializer: 'initialize(address)', + }) + await udt.connect(admin).mint(await delegater.getAddress(), 5000) + + // deploying timelock with a proxy + const UnlockProtocolTimelock = await ethers.getContractFactory( + 'UnlockProtocolTimelock' + ) + const timelock = await upgrades.deployProxy(UnlockProtocolTimelock, [ + 1, // 1 second delay + [], // proposers list is empty at deployment + [ADDRESS_ZERO], // allow any address to execute a proposal once the timelock has expired + ]) + + // deploy governor + const UnlockProtocolGovernor = await ethers.getContractFactory( + 'UnlockProtocolGovernor' + ) + gov = await upgrades.deployProxy(UnlockProtocolGovernor, [ + await udt.getAddress(), + SIX_HUNDRED_BLOCKS, + SIX_HUNDRED_BLOCKS, + '0', + await timelock.getAddress(), + ]) + + // grant role + await timelock.grantRole(PROPOSER_ROLE, await gov.getAddress()) + }) + + describe('Default values', () => { + it('default delay is set properly', async () => { + assert.equal(await gov.votingDelay(), votingDelay) + }) + + it('voting period is 1 week', async () => { + assert.equal(await gov.votingPeriod(), votingPeriod) + }) + }) + + describe('Update voting params', () => { + before(async () => { + await udt.connect(delegater).delegate(await voter.getAddress()) + + await increaseBlock() + }) + + it('should only be possible through voting', async () => { + assert.equal(await gov.votingDelay(), votingDelay) + await reverts(gov.setVotingDelay(2), 'Governor: onlyGovernance') + await reverts(gov.setVotingPeriod(2), 'Governor: onlyGovernance') + await reverts(gov.setQuorum(2), 'Governor: onlyGovernance') + }) + + describe('VotingPeriod', () => { + it('should be properly updated through voting', async () => { + const votingPeriod = SIX_HUNDRED_BLOCKS / 2 + const encoded = gov.interface.encodeFunctionData('setVotingPeriod', [ + votingPeriod, + ]) + + // propose + const proposal = [ + [await gov.getAddress()], + ['0'], + [encoded], + '', + ] + + const execTx = await launchVotingProcess(voter, proposal) + + const changed = await gov.votingPeriod() + assert.equal(changed == votingPeriod, true) + + // make sure event has been fired + const { args } = await getEvent(execTx, 'VotingPeriodUpdated') + const { newVotingPeriod } = args + assert.equal(newVotingPeriod == votingPeriod, true) + }) + }) + + describe('VotingDelay', () => { + it('should be properly updated through voting', async () => { + const votingDelay = SIX_HUNDRED_BLOCKS / 2 + const encoded = gov.interface.encodeFunctionData('setVotingDelay', [ + votingDelay, + ]) + + const proposal = [ + [await gov.getAddress()], + ['0'], + [encoded], + '', + ] + + const execReceipt = await launchVotingProcess(voter, proposal) + const changed = await gov.votingDelay() + assert.equal(changed == votingDelay, true) + + // make sure event has been fired + const { args } = await getEvent(execReceipt, 'VotingDelayUpdated') + const { newVotingDelay } = args + assert.equal(newVotingDelay, votingDelay) + }) + }) + + describe('setQuorum', () => { + it('should be properly updated through voting', async () => { + const quorum = SIX_HUNDRED_BLOCKS / 2 + const encoded = gov.interface.encodeFunctionData('setQuorum', [quorum]) + + const proposal = [ + [await gov.getAddress()], + ['0'], + [encoded], + '', + ] + + const execReceipt = await launchVotingProcess(voter, proposal) + const changed = await gov.quorum(0) + assert.equal(changed == quorum, true) + + // make sure event has been fired + const { args } = await getEvent(execReceipt, 'QuorumUpdated') + const { newQuorum } = args + assert.equal(newQuorum, quorum) + }) + }) + + describe('Cancel', () => { + it('should be properly cancelled', async () => { + const votingDelay = 10000n + const encoded = gov.interface.encodeFunctionData('setVotingDelay', [ + votingDelay, + ]) + + const proposal = [ + [await gov.getAddress()], + ['1'], + [encoded], + '', + ] + const proposalTx = await gov.propose(...proposal) + + const receipt = await proposalTx.wait() + const evt = await getEvent(receipt, 'ProposalCreated') + const { proposalId } = evt.args + + // proposal exists but does not accept votes yet + assert.equal(await gov.state(proposalId), 0) // Pending + + // get params + const descriptionHash = ethers.keccak256( + ethers.toUtf8Bytes(proposal.slice(-1).find(Boolean)) + ) + const [targets, values, calldatas] = proposal + + await gov.cancel(targets, values, calldatas, descriptionHash) + + assert.equal(await gov.state(proposalId), 2) // Canceled + }) + }) + + describe('supportsInterface', async () => { + it('should support id for IERC1155ReceiverUpgradeable', async () => { + const interfaceId = '0x4e2312e0' // Interface Id of IERC1155ReceiverUpgradeable.sol + const supportsGovernor = await gov.supportsInterface(interfaceId) + assert.equal(supportsGovernor, true) + }) + + it('should not support random interface', async function () { + const randomInterfaceId = '0x12345678' + const supportsRandom = await gov.supportsInterface(randomInterfaceId) + assert.equal(supportsRandom, false) + }) + }) + }) +}) diff --git a/smart-contracts/test/UnlockDiscountToken/upgrades.mainnet.js b/smart-contracts/test/UnlockDiscountToken/upgrades.mainnet.js index f272a5cd9a3..bb6288ecb1c 100644 --- a/smart-contracts/test/UnlockDiscountToken/upgrades.mainnet.js +++ b/smart-contracts/test/UnlockDiscountToken/upgrades.mainnet.js @@ -82,11 +82,11 @@ const upgradeContract = async () => { return UnlockDiscountTokenV3.attach(UDTProxyContractAddress) } -describe('UnlockDiscountToken (on mainnet)', async () => { +describe('UnlockDiscountToken (on mainnet)', () => { let udt let deployer - beforeEach(async function setupMainnetForkTestEnv() { + before(async function () { if (!process.env.RUN_FORK) { // all suite will be skipped this.skip() diff --git a/smart-contracts/test/helpers/block.js b/smart-contracts/test/helpers/block.js new file mode 100644 index 00000000000..e216ac64203 --- /dev/null +++ b/smart-contracts/test/helpers/block.js @@ -0,0 +1,11 @@ +const { ethers } = require('hardhat') + +async function increaseBlock(block = 1) { + for (let i = 0; i < block; i++) { + await ethers.provider.send('evm_mine') + } +} + +module.exports = { + increaseBlock, +} diff --git a/smart-contracts/test/helpers/index.js b/smart-contracts/test/helpers/index.js index e4030bf84be..78f4ba5e55d 100644 --- a/smart-contracts/test/helpers/index.js +++ b/smart-contracts/test/helpers/index.js @@ -16,6 +16,7 @@ const versions = require('./versions') const bridge = require('./bridge') const events = require('./events') const bigNumber = require('./bigNumber') +const block = require('./block') module.exports = { deployContracts, @@ -36,4 +37,5 @@ module.exports = { ...bridge, ...events, ...bigNumber, + ...block, } diff --git a/smart-contracts/test/mainnet/udt.js b/smart-contracts/test/mainnet/udt.mainnet.js similarity index 93% rename from smart-contracts/test/mainnet/udt.js rename to smart-contracts/test/mainnet/udt.mainnet.js index a1d684eaba7..6c764d7ff27 100644 --- a/smart-contracts/test/mainnet/udt.js +++ b/smart-contracts/test/mainnet/udt.mainnet.js @@ -1,25 +1,22 @@ const assert = require('assert') const { ethers } = require('hardhat') +const { reverts, ADDRESS_ZERO, advanceBlock } = require('../helpers') const { - reverts, - ADDRESS_ZERO, - getProxyAddress, - advanceBlock, -} = require('../helpers') -const { getEvent } = require('@unlock-protocol/hardhat-helpers') - -const { + getEvent, resetNodeState, + getUnlockAddress, + getUdt, impersonate, - MULTISIG_ADDRESS_OWNER, -} = require('../helpers') +} = require('@unlock-protocol/hardhat-helpers') + +const { MULTISIG_ADDRESS_OWNER } = require('../helpers') -describe('UnlockDiscountToken on mainnet', async () => { +describe('UnlockDiscountToken on mainnet', () => { let udt const chainId = 1 // mainnet let unlockAddress - beforeEach(async function setupMainnetForkTestEnv() { + before(async function setupMainnetForkTestEnv() { if (!process.env.RUN_FORK) { // all suite will be skipped this.skip() @@ -28,21 +25,13 @@ describe('UnlockDiscountToken on mainnet', async () => { // reset fork await resetNodeState() - // prepare proxy info - const proxyAddress = getProxyAddress(chainId, 'UnlockDiscountTokenV3') - // const proxyAdmin = getProxyAdminAddress({ network }) - // await impersonate(proxyAdmin) - // mocha settings this.timeout(200000) - const UnlockDiscountToken = await ethers.getContractFactory( - 'UnlockDiscountTokenV3' - ) const [, minter] = await ethers.getSigners() - udt = await UnlockDiscountToken.attach(proxyAddress).connect(minter) + udt = (await getUdt()).connect(minter) - unlockAddress = getProxyAddress(chainId, 'Unlock') + unlockAddress = await getUnlockAddress() }) describe('ERC20 details', () => { @@ -63,7 +52,9 @@ describe('UnlockDiscountToken on mainnet', async () => { }) describe('mint', () => { - const amount = ethers.hexStripZeros(ethers.parseEther('1000')) + const amount = ethers.stripZerosLeft( + ethers.toBeHex(ethers.parseEther('1000')) + ) it('minters can not be added anymore', async () => { const [, minter] = await ethers.getSigners() @@ -135,7 +126,7 @@ describe('UnlockDiscountToken on mainnet', async () => { assert.isTrue(pastTotalSupply == totalSupply) }) it('increases when tokens are minted', async () => { - const amount = ethers.hexStripZeros(ethers.parseEther('1000')) + const amount = ethers.parseEther('1000') const blockNumber = await ethers.provider.getBlockNumber() await advanceBlock() const pastTotalSupply = await udt.getPastTotalSupply(blockNumber) @@ -224,7 +215,6 @@ describe('UnlockDiscountToken on mainnet', async () => { const value = 1 const deadline = Math.floor(new Date().getTime()) + 60 * 60 * 24 - const { chainId } = await ethers.provider.getNetwork() const nonce = await udt.nonces(await permitter.getAddress()) const domain = {