From 43ca4e71fd4a78995beb673d543cee8aaf8c93ae Mon Sep 17 00:00:00 2001 From: Julien Genestoux Date: Fri, 19 Jan 2024 14:43:29 -0500 Subject: [PATCH] Production Deploy (#13222) * Manual deploy as of commit:603184e5b764369eb11f833907b8a7f79a0ddd13 * fix(locksmith): fixing slug creation when adding new event --- governance/.openzeppelin/sepolia.json | 333 ++- governance/scripts/deployments/oracle.js | 24 +- governance/scripts/deployments/udt.js | 30 +- governance/scripts/deployments/unlock.js | 2 +- .../controllers/v2/rsvpController.test.ts | 92 + .../operations/eventOperations.test.ts | 60 + .../migrations/20240118153529-create-rsvp.js | 46 + .../src/controllers/purchaseController.ts | 21 +- .../src/controllers/v2/rsvpController.ts | 93 + locksmith/src/models/Rsvp.ts | 63 + locksmith/src/models/index.ts | 1 + locksmith/src/operations/eventOperations.ts | 13 +- locksmith/src/routes/claim.ts | 2 +- locksmith/src/routes/index.ts | 2 + locksmith/src/routes/v2/rsvp.ts | 9 + .../utils/middlewares/recaptchaMiddleware.ts | 16 +- .../UnlockDiscountTokenV3.json | 768 +++++++ .../src/abis/utils/UniswapOracleV3.json | 143 ++ .../UnlockDiscountTokenV3.sol | 2044 +++++++++++++++++ .../src/contracts/utils/UniswapOracleV3.sol | 1177 ++++++++++ packages/contracts/src/index.ts | 10 +- packages/contracts/src/utils/parser.ts | 22 +- packages/email-templates/src/index.ts | 3 + .../src/templates/eventRsvpSubmitted.ts | 26 + .../src/templates/helpers/eventDetails.ts | 30 + packages/networks/src/networks/sepolia.ts | 3 + packages/unlock-js/CHANGELOG.md | 4 + packages/unlock-js/openapi.yml | 49 + packages/unlock-js/package.json | 2 +- packages/unlock-js/src/KeyManager.ts | 4 +- .../unlock-js/src/Unlock/v10/createLock.js | 5 +- .../unlock-js/src/Unlock/v11/createLock.js | 5 +- .../unlock-js/src/Unlock/v4/createLock.js | 5 +- .../unlock-js/src/Unlock/v6/createLock.js | 5 +- .../unlock-js/src/Unlock/v7/createLock.js | 5 +- .../unlock-js/src/Unlock/v8/createLock.js | 5 +- packages/unlock-js/src/abis.ts | 4 +- scripts/manual-production-pr.sh | 12 +- .../components/content/event/EventDetails.tsx | 4 +- .../src/components/content/event/Form.tsx | 193 +- .../event/Registration/EmbeddedCheckout.tsx | 35 + .../content/event/Registration/HasTicket.tsx | 16 + .../{ => Registration}/LockPriceDetails.tsx | 93 +- .../event/Registration/RegistrationCard.tsx | 70 + .../event/Registration/SingleLock/index.tsx | 129 ++ .../WalletlessRegistration.tsx | 265 ++- .../content/event/RegistrationCard.tsx | 137 -- .../interface/checkout/main/Captcha.tsx | 4 - .../interface/checkout/main/Metadata.tsx | 2 +- unlock-app/src/hooks/useRsvp.ts | 37 + 50 files changed, 5733 insertions(+), 390 deletions(-) create mode 100644 locksmith/__tests__/controllers/v2/rsvpController.test.ts create mode 100644 locksmith/__tests__/operations/eventOperations.test.ts create mode 100644 locksmith/migrations/20240118153529-create-rsvp.js create mode 100644 locksmith/src/controllers/v2/rsvpController.ts create mode 100644 locksmith/src/models/Rsvp.ts create mode 100644 locksmith/src/routes/v2/rsvp.ts create mode 100644 packages/contracts/src/abis/UnlockDiscountToken/UnlockDiscountTokenV3.json create mode 100644 packages/contracts/src/abis/utils/UniswapOracleV3.json create mode 100644 packages/contracts/src/contracts/UnlockDiscountToken/UnlockDiscountTokenV3.sol create mode 100644 packages/contracts/src/contracts/utils/UniswapOracleV3.sol create mode 100644 packages/email-templates/src/templates/eventRsvpSubmitted.ts create mode 100644 unlock-app/src/components/content/event/Registration/EmbeddedCheckout.tsx create mode 100644 unlock-app/src/components/content/event/Registration/HasTicket.tsx rename unlock-app/src/components/content/event/{ => Registration}/LockPriceDetails.tsx (75%) create mode 100644 unlock-app/src/components/content/event/Registration/RegistrationCard.tsx create mode 100644 unlock-app/src/components/content/event/Registration/SingleLock/index.tsx rename unlock-app/src/components/content/event/{ => Registration}/WalletlessRegistration.tsx (57%) delete mode 100644 unlock-app/src/components/content/event/RegistrationCard.tsx create mode 100644 unlock-app/src/hooks/useRsvp.ts diff --git a/governance/.openzeppelin/sepolia.json b/governance/.openzeppelin/sepolia.json index 7e9c1636792..21640906b54 100644 --- a/governance/.openzeppelin/sepolia.json +++ b/governance/.openzeppelin/sepolia.json @@ -14,6 +14,11 @@ "address": "0x338b1f296217485bf4df6CE9f93ab4C73F72b57D", "txHash": "0xd531fbac4a2fb3cc68868ad5a93ac3fdd4f7a52088f96174648535d7975fe224", "kind": "transparent" + }, + { + "address": "0x447B1492C5038203f1927eB2a374F5Fcdc25999d", + "txHash": "0xa552ab865e287870a8c75bca97243b7cdc1a73a60e3221c6198ffaec2cd155fa", + "kind": "transparent" } ], "impls": { @@ -276,6 +281,332 @@ } } }, + "6356f66d91b12f6ac3de084c05af120c5cce0edb65e6a6166ed49718631c377e": { + "address": "0xDcDE260Df00ba86889e8B112DfBe1A4945B35CA9", + "txHash": "0x978663fde295f428c59e824b7fd20fd9c75bf497c1606af7f5af39469024b874", + "layout": { + "solcVersion": "0.8.21", + "storage": [ + { + "label": "initialized", + "offset": 0, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "contracts/past-versions/UnlockDiscountTokenV3.sol:238" + }, + { + "label": "initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "contracts/past-versions/UnlockDiscountTokenV3.sol:243" + }, + { + "label": "______gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "contracts/past-versions/UnlockDiscountTokenV3.sol:295" + }, + { + "label": "_balances", + "offset": 0, + "slot": "51", + "type": "t_mapping(t_address,t_uint256)", + "contract": "ERC20Upgradeable", + "src": "contracts/past-versions/UnlockDiscountTokenV3.sol:330" + }, + { + "label": "_allowances", + "offset": 0, + "slot": "52", + "type": "t_mapping(t_address,t_mapping(t_address,t_uint256))", + "contract": "ERC20Upgradeable", + "src": "contracts/past-versions/UnlockDiscountTokenV3.sol:332" + }, + { + "label": "_totalSupply", + "offset": 0, + "slot": "53", + "type": "t_uint256", + "contract": "ERC20Upgradeable", + "src": "contracts/past-versions/UnlockDiscountTokenV3.sol:334" + }, + { + "label": "______gap", + "offset": 0, + "slot": "54", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC20Upgradeable", + "src": "contracts/past-versions/UnlockDiscountTokenV3.sol:689" + }, + { + "label": "_minters", + "offset": 0, + "slot": "104", + "type": "t_struct(Role)8996_storage", + "contract": "MinterRoleUpgradeable", + "src": "contracts/past-versions/UnlockDiscountTokenV3.sol:1828" + }, + { + "label": "______gap", + "offset": 0, + "slot": "105", + "type": "t_array(t_uint256)50_storage", + "contract": "MinterRoleUpgradeable", + "src": "contracts/past-versions/UnlockDiscountTokenV3.sol:1866" + }, + { + "label": "______gap", + "offset": 0, + "slot": "155", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC20MintableUpgradeable", + "src": "contracts/past-versions/UnlockDiscountTokenV3.sol:1916" + }, + { + "label": "_name", + "offset": 0, + "slot": "205", + "type": "t_string_storage", + "contract": "ERC20DetailedUpgradeable", + "src": "contracts/past-versions/UnlockDiscountTokenV3.sol:1870" + }, + { + "label": "_symbol", + "offset": 0, + "slot": "206", + "type": "t_string_storage", + "contract": "ERC20DetailedUpgradeable", + "src": "contracts/past-versions/UnlockDiscountTokenV3.sol:1871" + }, + { + "label": "_decimals", + "offset": 0, + "slot": "207", + "type": "t_uint8", + "contract": "ERC20DetailedUpgradeable", + "src": "contracts/past-versions/UnlockDiscountTokenV3.sol:1872" + }, + { + "label": "______gap", + "offset": 0, + "slot": "208", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC20DetailedUpgradeable", + "src": "contracts/past-versions/UnlockDiscountTokenV3.sol:1896" + }, + { + "label": "_HASHED_NAME", + "offset": 0, + "slot": "258", + "type": "t_bytes32", + "contract": "EIP712Upgradeable", + "src": "contracts/past-versions/UnlockDiscountTokenV3.sol:866" + }, + { + "label": "_HASHED_VERSION", + "offset": 0, + "slot": "259", + "type": "t_bytes32", + "contract": "EIP712Upgradeable", + "src": "contracts/past-versions/UnlockDiscountTokenV3.sol:867" + }, + { + "label": "__gap", + "offset": 0, + "slot": "260", + "type": "t_array(t_uint256)50_storage", + "contract": "EIP712Upgradeable", + "src": "contracts/past-versions/UnlockDiscountTokenV3.sol:984" + }, + { + "label": "_nonces", + "offset": 0, + "slot": "310", + "type": "t_mapping(t_address,t_struct(Counter)10192_storage)", + "contract": "ERC20PermitUpgradeable", + "src": "contracts/past-versions/UnlockDiscountTokenV3.sol:1048" + }, + { + "label": "_PERMIT_TYPEHASH", + "offset": 0, + "slot": "311", + "type": "t_bytes32", + "contract": "ERC20PermitUpgradeable", + "src": "contracts/past-versions/UnlockDiscountTokenV3.sol:1051" + }, + { + "label": "__gap", + "offset": 0, + "slot": "312", + "type": "t_array(t_uint256)49_storage", + "contract": "ERC20PermitUpgradeable", + "src": "contracts/past-versions/UnlockDiscountTokenV3.sol:1139" + }, + { + "label": "_delegates", + "offset": 0, + "slot": "361", + "type": "t_mapping(t_address,t_address)", + "contract": "ERC20VotesUpgradeable", + "src": "contracts/past-versions/UnlockDiscountTokenV3.sol:1502" + }, + { + "label": "_checkpoints", + "offset": 0, + "slot": "362", + "type": "t_mapping(t_address,t_array(t_struct(Checkpoint)10997_storage)dyn_storage)", + "contract": "ERC20VotesUpgradeable", + "src": "contracts/past-versions/UnlockDiscountTokenV3.sol:1503" + }, + { + "label": "_totalSupplyCheckpoints", + "offset": 0, + "slot": "363", + "type": "t_array(t_struct(Checkpoint)10997_storage)dyn_storage", + "contract": "ERC20VotesUpgradeable", + "src": "contracts/past-versions/UnlockDiscountTokenV3.sol:1504" + }, + { + "label": "__gap", + "offset": 0, + "slot": "364", + "type": "t_array(t_uint256)47_storage", + "contract": "ERC20VotesUpgradeable", + "src": "contracts/past-versions/UnlockDiscountTokenV3.sol:1761" + }, + { + "label": "__gap", + "offset": 0, + "slot": "411", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC20VotesCompUpgradeable", + "src": "contracts/past-versions/UnlockDiscountTokenV3.sol:1815" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_struct(Checkpoint)10997_storage)dyn_storage": { + "label": "struct ERC20VotesUpgradeable.Checkpoint[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)47_storage": { + "label": "uint256[47]", + "numberOfBytes": "1504" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_address)": { + "label": "mapping(address => address)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_array(t_struct(Checkpoint)10997_storage)dyn_storage)": { + "label": "mapping(address => struct ERC20VotesUpgradeable.Checkpoint[])", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_bool)": { + "label": "mapping(address => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_mapping(t_address,t_uint256))": { + "label": "mapping(address => mapping(address => uint256))", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_struct(Counter)10192_storage)": { + "label": "mapping(address => struct CountersUpgradeable.Counter)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_uint256)": { + "label": "mapping(address => uint256)", + "numberOfBytes": "32" + }, + "t_string_storage": { + "label": "string", + "numberOfBytes": "32" + }, + "t_struct(Checkpoint)10997_storage": { + "label": "struct ERC20VotesUpgradeable.Checkpoint", + "members": [ + { + "label": "fromBlock", + "type": "t_uint32", + "offset": 0, + "slot": "0" + }, + { + "label": "votes", + "type": "t_uint224", + "offset": 4, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(Counter)10192_storage": { + "label": "struct CountersUpgradeable.Counter", + "members": [ + { + "label": "_value", + "type": "t_uint256", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(Role)8996_storage": { + "label": "struct Roles.Role", + "members": [ + { + "label": "bearer", + "type": "t_mapping(t_address,t_bool)", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_uint224": { + "label": "uint224", + "numberOfBytes": "28" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint32": { + "label": "uint32", + "numberOfBytes": "4" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + }, + "namespaces": {} + } + }, "fb605bbc63d9dcc4472ae5112fbd437d30579cd6c5562ea3ae643850762d8e95": { "address": "0xe87eFc02F26EFE45171afDBEc85D743FDB2Eb1FB", "txHash": "0xa87aef9a4a19296127c447cf245c15c4540c7fb2a51df811db95fc4f3e0accf4", @@ -687,4 +1018,4 @@ } } } -} +} \ No newline at end of file diff --git a/governance/scripts/deployments/oracle.js b/governance/scripts/deployments/oracle.js index 66637bdb4a0..6287a0ba09d 100644 --- a/governance/scripts/deployments/oracle.js +++ b/governance/scripts/deployments/oracle.js @@ -1,21 +1,23 @@ const { ethers } = require('hardhat') const UniswapOracleV2 = require('@unlock-protocol/hardhat-helpers/dist/ABIs/UniswapV2Oracle.json') +const { UniswapOracleV3 } = require('@unlock-protocol/contracts') const { getNetwork } = require('@unlock-protocol/hardhat-helpers') // TODO: check if oracle has already been deployed and skips if one already exists! async function main({ uniswapFactoryAddress, uniswapVersion = 3 } = {}) { if (!uniswapFactoryAddress) { + console.log(await getNetwork()) const { uniswapV3: { factoryAddress }, } = await getNetwork() uniswapFactoryAddress = factoryAddress } - if (uniswapVersion == 2 && !uniswapFactoryAddress) { + if (!uniswapFactoryAddress) { // eslint-disable-next-line no-console throw new Error( - 'UNISWAP ORACLE > Missing Uniswap V2 Factory address... aborting.' + 'UNISWAP ORACLE > Missing Uniswap Factory address... aborting.' ) } @@ -26,20 +28,24 @@ async function main({ uniswapFactoryAddress, uniswapVersion = 3 } = {}) { UniswapOracleV2.bytecode ) } else if (uniswapVersion == 3) { - Oracle = await ethers.getContractFactory('UniswapOracleV3') + Oracle = await ethers.getContractFactory( + UniswapOracleV3.abi, + UniswapOracleV3.bytecode + ) } const oracle = await Oracle.deploy(uniswapFactoryAddress) - await oracle.deployed() + await oracle.waitForDeployment() + const { hash } = await oracle.deploymentTransaction() + + // get addresses + const oracleAddress = await oracle.getAddress() - // eslint-disable-next-line no-console console.log( - 'UNISWAP ORACLE > Oracle deployed at:', - oracle.address, - ` (tx: ${oracle.deployTransaction.hash})` + `UNISWAP ORACLE > Oracle deployed at ${oracleAddress} (tx: ${hash})` ) - return oracle.address + return oracleAddress } // execute as standalone diff --git a/governance/scripts/deployments/udt.js b/governance/scripts/deployments/udt.js index 31576818b98..a6d8bb47c4a 100644 --- a/governance/scripts/deployments/udt.js +++ b/governance/scripts/deployments/udt.js @@ -1,20 +1,40 @@ -const { ethers, upgrades } = require('hardhat') +const { ethers, upgrades, run } = require('hardhat') +const { + copyAndBuildContractsAtVersion, + isLocalhost, +} = require('@unlock-protocol/hardhat-helpers') async function main() { const [minter] = await ethers.getSigners() - const UDT = await ethers.getContractFactory('UnlockDiscountTokenV3') + await copyAndBuildContractsAtVersion(__dirname, [ + { + contractName: 'UnlockDiscountToken', + version: 3, + }, + ]) + + const UDT = await ethers.getContractFactory( + 'contracts/past-versions/UnlockDiscountTokenV3.sol:UnlockDiscountTokenV3' + ) + const udt = await upgrades.deployProxy(UDT, [minter.address], { initializer: 'initialize(address)', }) - await udt.deployed() + await udt.waitForDeployment() + const { hash } = await udt.deploymentTransaction() + const udtAddress = await udt.getAddress() // eslint-disable-next-line no-console console.log( - `UDT SETUP > UDT v3 (w proxy) deployed to: ${udt.address} (tx: ${udt.deployTransaction.hash})` + `UDT SETUP > UDT v3 (w proxy) deployed to: ${udtAddress} (tx: ${hash})` ) - return udt.address + if (!(await isLocalhost())) { + await run('verify:verify', { address: udtAddress }) + } + + return udtAddress } // execute as standalone diff --git a/governance/scripts/deployments/unlock.js b/governance/scripts/deployments/unlock.js index a8890b9b5f2..1f518af2316 100644 --- a/governance/scripts/deployments/unlock.js +++ b/governance/scripts/deployments/unlock.js @@ -39,7 +39,7 @@ async function main({ unlockVersion } = {}) { `- implementation at: ${implementation}` ) - if (!isLocalhost()) { + if (!(await isLocalhost())) { await run('verify:verify', { address: implementation }) } diff --git a/locksmith/__tests__/controllers/v2/rsvpController.test.ts b/locksmith/__tests__/controllers/v2/rsvpController.test.ts new file mode 100644 index 00000000000..14cfa72b67e --- /dev/null +++ b/locksmith/__tests__/controllers/v2/rsvpController.test.ts @@ -0,0 +1,92 @@ +import request from 'supertest' +import { loginRandomUser } from '../../test-helpers/utils' +import * as z from 'zod' +import logger from '../../../src/logger' +import app from '../../app' +import { vi } from 'vitest' +import { SupplierBody } from '../../../src/controllers/v2/receiptBaseController' +import { PurchaserBody } from '../../../src/controllers/v2/receiptController' +import { ethers } from 'ethers' +import { Rsvp } from '../../../src/models' + +const lockAddress = '0x62CcB13A72E6F991dE53b9B7AC42885151588Cd2' +const network = 5 + +describe('RSVP', () => { + beforeEach(async () => { + await Rsvp.truncate() + }) + it('stores the RSVP in the right table', async () => { + expect.assertions(5) + const response = await request(app) + .post(`/v2/rsvp/${network}/${lockAddress}/`) + .send({ + recipient: '0x81Dd955D02D337DB81BA6c9C5F6213E647672052', + email: 'julien@unlock-protocol.com', + data: { + fullname: 'Julien Genestoux', + }, + }) + + expect(response.status).toBe(200) + expect(response.body.lockAddress).toEqual(lockAddress) + expect(response.body.userAddress).toEqual( + '0x81Dd955D02D337DB81BA6c9C5F6213E647672052' + ) + expect(response.body.approval).toEqual('pending') + expect(response.body.network).toEqual('5') + }) + + it('stores the RSVP in the right table even if there is no wallet', async () => { + expect.assertions(3) + const response = await request(app) + .post(`/v2/rsvp/${network}/${lockAddress}/`) + .send({ + email: 'julien@unlock-protocol.com', + data: { + fullname: 'Julien Genestoux', + }, + }) + + expect(response.status).toBe(200) + expect(response.body.approval).toBe('pending') + expect(response.body.userAddress).toEqual( + '0xa00B5f0Eb8b6D009A30D1510785F1383691D4829' + ) + }) + + it('does not override the state of an approved participant', async () => { + expect.assertions(4) + const response = await request(app) + .post(`/v2/rsvp/${network}/${lockAddress}/`) + .send({ + email: 'ccarfi@unlock-protocol.com', + data: { + fullname: 'Chris Carfi', + }, + }) + + expect(response.status).toBe(200) + expect(response.body.approval).toEqual('pending') + + const rsvp = await Rsvp.findOne({ + where: { + userAddress: response.body.userAddress, + lockAddress: response.body.lockAddress, + network: response.body.network, + }, + }) + expect(rsvp!.approval).toEqual('pending') + rsvp!.approval = 'approved' + await rsvp?.save() + const responseAfterUpdate = await request(app) + .post(`/v2/rsvp/${network}/${lockAddress}/`) + .send({ + email: 'ccarfi@unlock-protocol.com', + data: { + fullname: 'Chris Carfi', + }, + }) + expect(responseAfterUpdate.body.approval).toEqual('approved') + }) +}) diff --git a/locksmith/__tests__/operations/eventOperations.test.ts b/locksmith/__tests__/operations/eventOperations.test.ts new file mode 100644 index 00000000000..347982b6d70 --- /dev/null +++ b/locksmith/__tests__/operations/eventOperations.test.ts @@ -0,0 +1,60 @@ +import { CheckoutConfig, EventData } from '../../src/models' +import { + createEventSlug, + saveEvent, +} from '../../src/operations/eventOperations' +describe('eventOperations', () => { + beforeEach(async () => { + await EventData.truncate() + await CheckoutConfig.truncate() + }) + describe('createEventSlug', () => { + it('should merge keys items with the corresponding metadata', async () => { + expect.assertions(2) + const slug = await createEventSlug('Exclusive event') + expect(slug).toEqual('exclusive-event') + await EventData.create({ + name: 'Exclusive event', + slug, + data: {}, + createdBy: '0x123', + }) + const anotherSlug = await createEventSlug('Exclusive event') + expect(anotherSlug).toEqual('exclusive-event-1') + }) + }) + + describe('saveEvent', () => { + it('should not override an event if no slug is provided', async () => { + expect.assertions(2) + const eventParams = { + data: { name: 'my party' }, + checkoutConfig: { config: { locks: {} } }, + } + const [event] = await saveEvent(eventParams, '0x123') + expect(event.slug).toEqual('my-party') + const [anotherEvent] = await saveEvent(eventParams, '0x123') + expect(anotherEvent.slug).toEqual('my-party-1') + }) + + it('should override an event if a slug is provided', async () => { + expect.assertions(1) + const eventParams = { + data: { name: 'my party' }, + checkoutConfig: { config: { locks: {} } }, + } + const [event] = await saveEvent(eventParams, '0x123') + const [sameEvent] = await saveEvent( + { + data: { + slug: event.slug, + name: 'name changed!', + }, + checkoutConfig: { config: { locks: {} } }, + }, + '0x123' + ) + expect(sameEvent.slug).toEqual(event.slug) + }) + }) +}) diff --git a/locksmith/migrations/20240118153529-create-rsvp.js b/locksmith/migrations/20240118153529-create-rsvp.js new file mode 100644 index 00000000000..60297a144b7 --- /dev/null +++ b/locksmith/migrations/20240118153529-create-rsvp.js @@ -0,0 +1,46 @@ +'use strict' +/** @type {import('sequelize-cli').Migration} */ +module.exports = { + async up(queryInterface, Sequelize) { + await queryInterface.createTable('Rsvps', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER, + }, + network: { + allowNull: false, + type: 'pg_chain_id', + }, + lockAddress: { + allowNull: false, + type: Sequelize.STRING, + }, + userAddress: { + allowNull: false, + type: Sequelize.STRING, + }, + approval: { + type: Sequelize.STRING, + allowNull: false, + default: 'pending', + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE, + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE, + }, + }) + await queryInterface.addIndex('Rsvps', { + unique: true, + fields: ['lockAddress', 'network', 'approval'], + }) + }, + async down(queryInterface, Sequelize) { + await queryInterface.dropTable('Rsvps') + }, +} diff --git a/locksmith/src/controllers/purchaseController.ts b/locksmith/src/controllers/purchaseController.ts index 4d52e46cdc2..fb525b0eebc 100644 --- a/locksmith/src/controllers/purchaseController.ts +++ b/locksmith/src/controllers/purchaseController.ts @@ -236,12 +236,21 @@ export class PurchaseController { const pricer = new KeyPricer() const fulfillmentDispatcher = new Dispatcher() - const totalAmount = await getTotalPurchasePriceInCrypto({ - lockAddress, - network, - recipients, - data: data || [], - }) + const [totalAmount, soldOut] = await Promise.all([ + getTotalPurchasePriceInCrypto({ + lockAddress, + network, + recipients, + data: data || [], + }), + isSoldOut(lockAddress, network, recipients.length), + ]) + + if (soldOut) { + return response.status(400).send({ + message: 'Lock is sold out', + }) + } if (totalAmount.gt(0)) { return response.status(400).send({ diff --git a/locksmith/src/controllers/v2/rsvpController.ts b/locksmith/src/controllers/v2/rsvpController.ts new file mode 100644 index 00000000000..442c59da561 --- /dev/null +++ b/locksmith/src/controllers/v2/rsvpController.ts @@ -0,0 +1,93 @@ +import { z } from 'zod' +import normalizer from '../../utils/normalizer' +import { Request, Response } from 'express' +import { upsertUserMetadata } from '../../operations/userMetadataOperations' +import { KeyManager } from '@unlock-protocol/unlock-js' +import { UserMetadata } from './metadataController' +import { Rsvp } from '../../models' +import { sendEmail } from '../../operations/wedlocksOperations' +import { getEventDataForLock } from '../../operations/eventOperations' + +const RsvpBody = z.object({ + data: z.record(z.string(), z.string()), + recipient: z + .string() + .optional() + .transform((item) => normalizer.ethereumAddress(item)), + email: z + .string() + .email() + .transform((value) => value.toLowerCase()), +}) + +export const rsvp = async (request: Request, response: Response) => { + const lockAddress = normalizer.ethereumAddress(request.params.lockAddress) + const network = Number(request.params.network) + const { recipient, data, email } = await RsvpBody.parseAsync(request.body) + + // By default we protect all metadata + const protectedMetadata = { + ...data, + email, + } + + let userAddress = recipient + // Support for walletless RSVP + if (!recipient && email) { + // We can build a recipient wallet address from the email address + const keyManager = new KeyManager() + userAddress = keyManager.createTransferAddress({ + params: { + email, + lockAddress, + }, + }) + } + + // Ok cool, then for each record, let's store the UserTokenMetadata + // And then let's just add the network, lockAddress, userAddress to a new Table called RSVPs, with a state? + const metadata = await UserMetadata.parseAsync({ + public: {}, + protected: { + ...protectedMetadata, + }, + }) + await upsertUserMetadata({ + network, + userAddress, + lockAddress, + metadata, + }) + + const [rsvp, created] = await Rsvp.findOrCreate({ + where: { + network, + userAddress, + lockAddress, + }, + defaults: { + network, + userAddress, + lockAddress, + approval: 'pending', + }, + }) + if (created) { + const eventDetail = await getEventDataForLock(lockAddress, network) + await sendEmail({ + network, + template: 'eventRsvpSubmitted', + failoverTemplate: 'eventRsvpSubmitted', + recipient: email, + // @ts-expect-error + params: { + eventName: eventDetail?.eventName, + eventDate: eventDetail?.eventDate, + eventTime: eventDetail?.eventTime, + eventUrl: eventDetail?.eventUrl || '', + }, + attachments: [], + }) + } + return response.status(200).send(rsvp.toJSON()) +} diff --git a/locksmith/src/models/Rsvp.ts b/locksmith/src/models/Rsvp.ts new file mode 100644 index 00000000000..2d9ff884d91 --- /dev/null +++ b/locksmith/src/models/Rsvp.ts @@ -0,0 +1,63 @@ +import type { + CreationOptional, + InferAttributes, + InferCreationAttributes, +} from 'sequelize' +import { Model, DataTypes } from 'sequelize' +import { sequelize } from './index' + +export class Rsvp extends Model< + InferAttributes, + InferCreationAttributes +> { + declare lockAddress: string + declare userAddress: string + declare network: number + declare approval: string + declare createdAt: CreationOptional + declare updatedAt: CreationOptional +} + +Rsvp.init( + { + lockAddress: { + allowNull: false, + type: DataTypes.STRING, + }, + userAddress: { + allowNull: false, + type: DataTypes.STRING, + }, + network: { + allowNull: false, + type: 'pg_chain_id', + }, + approval: { + type: DataTypes.STRING, + }, + createdAt: { + allowNull: false, + type: DataTypes.DATE, + }, + updatedAt: { + allowNull: false, + type: DataTypes.DATE, + }, + }, + { + sequelize, + indexes: [ + { + name: 'lock_user_network', + fields: ['lockAddress', 'userAddress', 'network'], + unique: true, + }, + { + name: 'approval_lock_network', + fields: ['lockAddress', 'network', 'approval'], + }, + ], + modelName: 'Rsvp', + tableName: 'Rsvps', + } +) diff --git a/locksmith/src/models/index.ts b/locksmith/src/models/index.ts index 1d717ec1be6..0d2e84ae30a 100644 --- a/locksmith/src/models/index.ts +++ b/locksmith/src/models/index.ts @@ -19,5 +19,6 @@ export * from './keyMetadata' export * from './checkoutConfig' export * from './Session' export * from './UnsubscribeList' +export * from './Rsvp' export * from './role' export * from './Event' diff --git a/locksmith/src/operations/eventOperations.ts b/locksmith/src/operations/eventOperations.ts index 3c41baa9cd1..0a66061027f 100644 --- a/locksmith/src/operations/eventOperations.ts +++ b/locksmith/src/operations/eventOperations.ts @@ -140,13 +140,12 @@ export const getEventBySlug = async (slug: string) => { export const createEventSlug = async ( name: string, - eventId?: number, index: number | undefined = undefined ): Promise => { const slug = index ? kebabCase([name, index].join('-')) : kebabCase(name) const event = await getEventBySlug(slug) - if (!!event && event.id !== eventId) { - return createEventSlug(name, eventId, index ? index + 1 : 1) + if (event) { + return createEventSlug(name, index ? index + 1 : 1) } return slug } @@ -155,12 +154,9 @@ export const saveEvent = async ( parsed: EventBodyType, walletAddress: string ): Promise<[EventData, boolean]> => { - const slug = - parsed.data.slug || (await createEventSlug(parsed.data.name, parsed.id)) - + const slug = parsed.data.slug || (await createEventSlug(parsed.data.name)) const [savedEvent, created] = await EventData.upsert( { - id: parsed.id, name: parsed.data.name, slug, data: { @@ -173,13 +169,12 @@ export const saveEvent = async ( conflictFields: ['slug'], } ) - if (!savedEvent.checkoutConfigId) { const checkoutConfig = await PaywallConfig.strip().parseAsync( parsed.checkoutConfig.config ) const createdConfig = await saveCheckoutConfig({ - name: `Checkout config for ${savedEvent.name}`, + name: `Checkout config for ${savedEvent.name} (${savedEvent.slug})`, config: checkoutConfig, createdBy: walletAddress, }) diff --git a/locksmith/src/routes/claim.ts b/locksmith/src/routes/claim.ts index 99a965397c8..d060c4e88cf 100644 --- a/locksmith/src/routes/claim.ts +++ b/locksmith/src/routes/claim.ts @@ -6,7 +6,7 @@ const purchaseController = new PurchaseController() const router = express.Router({ mergeParams: true }) // Disallow claim due to spam and bot activity -const geoRestriction = createGeoRestriction(['RU', 'UA']) +const geoRestriction = createGeoRestriction([]) router.post( '/:network/locks/:lockAddress', diff --git a/locksmith/src/routes/index.ts b/locksmith/src/routes/index.ts index 78942d20f85..410f69143fd 100644 --- a/locksmith/src/routes/index.ts +++ b/locksmith/src/routes/index.ts @@ -38,6 +38,7 @@ import hooksRooter from './v2/hooks' import emailSubscriptionRouter from './v2/emailSubscriptions' import { createCacheMiddleware } from '../utils/middlewares/cacheMiddleware' import magicEdenRouter from './v2/magicEden' +import rsvpRouter from './v2/rsvp' const router = express.Router({ mergeParams: true }) @@ -106,6 +107,7 @@ router.use('/v2/events', eventsRouter) router.use('/v2/hooks', hooksRooter) router.use('/v2/email-subscriptions', emailSubscriptionRouter) router.use('/v2/magic-eden', magicEdenRouter) +router.use('/v2/rsvp', rsvpRouter) router.use('/', (_, res) => { res.send('Unlock Protocol') diff --git a/locksmith/src/routes/v2/rsvp.ts b/locksmith/src/routes/v2/rsvp.ts new file mode 100644 index 00000000000..08eea536029 --- /dev/null +++ b/locksmith/src/routes/v2/rsvp.ts @@ -0,0 +1,9 @@ +import express from 'express' +import { captchaMiddleware } from '../../utils/middlewares/recaptchaMiddleware' +import { rsvp } from '../../controllers/v2/rsvpController' + +const router = express.Router({ mergeParams: true }) + +router.post('/:network/:lockAddress', captchaMiddleware, rsvp) + +export default router diff --git a/locksmith/src/utils/middlewares/recaptchaMiddleware.ts b/locksmith/src/utils/middlewares/recaptchaMiddleware.ts index 93246e5ab9c..691e3670d08 100644 --- a/locksmith/src/utils/middlewares/recaptchaMiddleware.ts +++ b/locksmith/src/utils/middlewares/recaptchaMiddleware.ts @@ -1,22 +1,26 @@ import { RequestHandler } from 'express' import fetch from 'isomorphic-fetch' -import config from '../../config/config' +import config, { isProduction, isStaging } from '../../config/config' import normalizer from '../normalizer' +import logger from '../../logger' /** * A list of authenticated users who are making calls for which we should bypass the captcha veification */ -const allowList = [ - '0xFac55e21630b08B58119C58AA5a7f808424D777e', // Protocol Labs - '0xEedb7dd2D6317F31E4ECB60ED5f4c8971e2E4FF9', // Protocol Labs - '0xAA5E881Ca7c2d4e0253b61A89D0086E71ce9cb1e', // Protocol Labs -].map((address: string) => normalizer.ethereumAddress(address)) +const allowList = [].map((address: string) => + normalizer.ethereumAddress(address) +) export const captchaMiddleware: RequestHandler = async ( request, response, next ) => { + if (!isProduction && !isStaging) { + logger.debug('Skip captcha in development') + return next() + } + if ( request.user?.walletAddress && allowList.indexOf(normalizer.ethereumAddress(request.user?.walletAddress)) > diff --git a/packages/contracts/src/abis/UnlockDiscountToken/UnlockDiscountTokenV3.json b/packages/contracts/src/abis/UnlockDiscountToken/UnlockDiscountTokenV3.json new file mode 100644 index 00000000000..9bd8fe53e53 --- /dev/null +++ b/packages/contracts/src/abis/UnlockDiscountToken/UnlockDiscountTokenV3.json @@ -0,0 +1,768 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "UnlockDiscountTokenV3", + "sourceName": "contracts/UnlockDiscountTokenV3.flatten.sol", + "abi": [ + { + "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": true, + "internalType": "address", + "name": "delegator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "fromDelegate", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "toDelegate", + "type": "address" + } + ], + "name": "DelegateChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "delegate", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "previousBalance", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newBalance", + "type": "uint256" + } + ], + "name": "DelegateVotesChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "MinterAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "MinterRemoved", + "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": "DOMAIN_SEPARATOR", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "addMinter", + "outputs": [], + "stateMutability": "nonpayable", + "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": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint32", + "name": "pos", + "type": "uint32" + } + ], + "name": "checkpoints", + "outputs": [ + { + "components": [ + { + "internalType": "uint32", + "name": "fromBlock", + "type": "uint32" + }, + { + "internalType": "uint224", + "name": "votes", + "type": "uint224" + } + ], + "internalType": "struct ERC20VotesUpgradeable.Checkpoint", + "name": "", + "type": "tuple" + } + ], + "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": "delegatee", + "type": "address" + } + ], + "name": "delegate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "delegatee", + "type": "address" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expiry", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "delegateBySig", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "delegates", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "getCurrentVotes", + "outputs": [ + { + "internalType": "uint96", + "name": "", + "type": "uint96" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + } + ], + "name": "getPastTotalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + } + ], + "name": "getPastVotes", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + } + ], + "name": "getPriorVotes", + "outputs": [ + { + "internalType": "uint96", + "name": "", + "type": "uint96" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "getVotes", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "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": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "symbol", + "type": "string" + }, + { + "internalType": "uint8", + "name": "decimals", + "type": "uint8" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_minter", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "initialize2", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "isMinter", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "mint", + "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": "address", + "name": "owner", + "type": "address" + } + ], + "name": "nonces", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "numCheckpoints", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "permit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceMinter", + "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" + } + ], + "bytecode": "0x608060405234801561000f575f80fd5b506127a68061001d5f395ff3fe608060405234801561000f575f80fd5b50600436106101a0575f3560e01c8063782d6fe1116100eb578063a9059cbb1161008f578063a9059cbb14610395578063aa271e1a146103a8578063b4b5ea57146103bb578063c3cda520146103ce578063c4d66de8146103e1578063d505accf146103f4578063dd62ed3e14610407578063f1127ed81461043f575f80fd5b8063782d6fe1146102fb5780637ecebe00146103265780638e539e8c1461033957806395d89b411461034c578063983b2d561461035457806398650275146103675780639ab24eb01461036f578063a457c2d714610382575f80fd5b8063395093511161015257806339509351146102415780633a46b1a81461025457806340c10f1914610267578063472abf681461027a578063587cde1e146102825780635c19a95c146102ad5780636fcfff45146102c057806370a08231146102e8575f80fd5b806306fdde03146101a4578063095ea7b3146101c25780631624f6c6146101e557806318160ddd146101fa57806323b872dd1461020c578063313ce5671461021f5780633644e51514610239575b5f80fd5b6101ac61047c565b6040516101b991906121a6565b60405180910390f35b6101d56101d036600461220c565b61048b565b60405190151581526020016101b9565b6101f86101f33660046122e1565b6104a1565b005b6035545b6040519081526020016101b9565b6101d561021a366004612350565b61053d565b6102276105e5565b60405160ff90911681526020016101b9565b6101fe6105f2565b6101d561024f36600461220c565b6105fb565b6101fe61026236600461220c565b610636565b6101d561027536600461220c565b61067f565b6101f86106af565b610295610290366004612389565b6107ca565b6040516001600160a01b0390911681526020016101b9565b6101f86102bb366004612389565b6107e8565b6102d36102ce366004612389565b6107f5565b60405163ffffffff90911681526020016101b9565b6101fe6102f6366004612389565b610817565b61030e61030936600461220c565b610831565b6040516001600160601b0390911681526020016101b9565b6101fe610334366004612389565b610844565b6101fe6103473660046123a2565b610862565b6101ac61088e565b6101f8610362366004612389565b610898565b6101f86108c6565b6101fe61037d366004612389565b6108cf565b6101d561039036600461220c565b610953565b6101d56103a336600461220c565b6109eb565b6101d56103b6366004612389565b6109f7565b61030e6103c9366004612389565b610a03565b6101f86103dc3660046123b9565b610a10565b6101f86103ef366004612389565b610b42565b6101f861040236600461240d565b610c17565b6101fe610415366004612472565b6001600160a01b039182165f90815260346020908152604080832093909416825291909152205490565b61045261044d3660046124a3565b610d5b565b60408051825163ffffffff1681526020928301516001600160e01b031692810192909252016101b9565b6060610486610ddc565b905090565b5f610497338484610e6c565b5060015b92915050565b5f54610100900460ff16806104b857505f5460ff16155b6104dd5760405162461bcd60e51b81526004016104d4906124e0565b60405180910390fd5b5f54610100900460ff161580156104fd575f805461ffff19166101011790555b60cd61050985826125a5565b5060ce61051684826125a5565b5060cf805460ff191660ff84161790558015610537575f805461ff00191690555b50505050565b5f610549848484610f8f565b6001600160a01b0384165f908152603460209081526040808320338452909152902054828110156105cd5760405162461bcd60e51b815260206004820152602860248201527f45524332303a207472616e7366657220616d6f756e74206578636565647320616044820152676c6c6f77616e636560c01b60648201526084016104d4565b6105da8533858403610e6c565b506001949350505050565b5f61048660cf5460ff1690565b5f61048661103f565b335f8181526034602090815260408083206001600160a01b03871684529091528120549091610497918590610631908690612675565b610e6c565b5f4382106106565760405162461bcd60e51b81526004016104d490612688565b6001600160a01b0383165f90815261016a6020526040902061067890836110ba565b9392505050565b5f610689336109f7565b6106a55760405162461bcd60e51b81526004016104d4906126bf565b6104978383611171565b5f6106ba6101025490565b146107395760405162461bcd60e51b815260206004820152604360248201527f416c726561647920696e697469616c697a65643a204549503731325f696e697460448201527f2c2045524332305065726d69745f696e69742c204552433230566f7465735f696064820152621b9a5d60ea1b608482015260a4016104d4565b61078d61074461047c565b6040805180820190915260018152603160f81b6020918201528151910120610102557fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc661010355565b6107c061079861047c565b507f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c961013755565b6107c861117b565b565b6001600160a01b039081165f90815261016960205260409020541690565b6107f233826111f0565b50565b6001600160a01b0381165f90815261016a602052604081205461049b9061126e565b6001600160a01b03165f9081526033602052604090205490565b5f61067861083f8484610636565b6112d6565b6001600160a01b0381165f908152610136602052604081205461049b565b5f4382106108825760405162461bcd60e51b81526004016104d490612688565b61049b61016b836110ba565b606061048661133d565b6108a1336109f7565b6108bd5760405162461bcd60e51b81526004016104d4906126bf565b6107f28161134c565b6107c83361138d565b6001600160a01b0381165f90815261016a60205260408120548015610941576001600160a01b0383165f90815261016a6020526040902061091160018361270f565b8154811061092157610921612722565b5f91825260209091200154600160201b90046001600160e01b0316610943565b5f5b6001600160e01b03169392505050565b335f9081526034602090815260408083206001600160a01b0386168452909152812054828110156109d45760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b60648201526084016104d4565b6109e13385858403610e6c565b5060019392505050565b5f610497338484610f8f565b5f61049b6068836113ce565b5f61049b61083f836108cf565b83421115610a605760405162461bcd60e51b815260206004820152601d60248201527f4552433230566f7465733a207369676e6174757265206578706972656400000060448201526064016104d4565b604080517fe48329057bfd03d55e49b547132e39cffd9c1820ad7b9d4c5307691425d15adf60208201526001600160a01b0388169181019190915260608101869052608081018590525f90610ad990610ad19060a0016040516020818303038152906040528051906020012061144f565b85858561149b565b9050610ae481611631565b8614610b2e5760405162461bcd60e51b81526020600482015260196024820152784552433230566f7465733a20696e76616c6964206e6f6e636560381b60448201526064016104d4565b610b3881886111f0565b505b505050505050565b5f54610100900460ff1680610b5957505f5460ff16155b610b755760405162461bcd60e51b81526004016104d4906124e0565b5f54610100900460ff16158015610b95575f805461ffff19166101011790555b610b9e82611659565b610bf2604051806040016040528060158152602001742ab73637b1b5902234b9b1b7bab73a102a37b5b2b760591b8152506040518060400160405280600381526020016215511560ea1b81525060126104a1565b610c02610bfd61047c565b6116b5565b8015610c13575f805461ff00191690555b5050565b83421115610c675760405162461bcd60e51b815260206004820152601d60248201527f45524332305065726d69743a206578706972656420646561646c696e6500000060448201526064016104d4565b5f61013754888888610c788c611631565b6040805160208101969096526001600160a01b0394851690860152929091166060840152608083015260a082015260c0810186905260e0016040516020818303038152906040528051906020012090505f610cd28261144f565b90505f610ce18287878761149b565b9050896001600160a01b0316816001600160a01b031614610d445760405162461bcd60e51b815260206004820152601e60248201527f45524332305065726d69743a20696e76616c6964207369676e6174757265000060448201526064016104d4565b610d4f8a8a8a610e6c565b50505050505050505050565b604080518082019091525f80825260208201526001600160a01b0383165f90815261016a60205260409020805463ffffffff8416908110610d9e57610d9e612722565b5f9182526020918290206040805180820190915291015463ffffffff81168252600160201b90046001600160e01b0316918101919091529392505050565b606060cd8054610deb9061252e565b80601f0160208091040260200160405190810160405280929190818152602001828054610e179061252e565b8015610e625780601f10610e3957610100808354040283529160200191610e62565b820191905f5260205f20905b815481529060010190602001808311610e4557829003601f168201915b5050505050905090565b6001600160a01b038316610ece5760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b60648201526084016104d4565b6001600160a01b038216610f2f5760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b60648201526084016104d4565b6001600160a01b038381165f8181526034602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a3505050565b738c769a59f93dac14b7a416294124c01d3ec4daac6001600160a01b0383161480610fd6575073cc06dd348169d95b1693b9185ca561b28f5b21656001600160a01b038316145b15610ff35773a39b44c4affbb56b76a1bf1d19eb93a5dfc2eba991505b6001600160a01b0383167388ad09518695c6c3712ac10a214be5109a6556710361102f5773a39b44c4affbb56b76a1bf1d19eb93a5dfc2eba991505b61103a83838361173c565b505050565b5f6104867f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f61106e6101025490565b610103546040805160208101859052908101839052606081018290524660808201523060a08201525f9060c0016040516020818303038152906040528051906020012090509392505050565b81545f90815b8181101561111b575f6110d38284611919565b9050848682815481106110e8576110e8612722565b5f9182526020909120015463ffffffff16111561110757809250611115565b611112816001612675565b91505b506110c0565b811561115d578461112d60018461270f565b8154811061113d5761113d612722565b5f91825260209091200154600160201b90046001600160e01b031661115f565b5f5b6001600160e01b031695945050505050565b610c13828261196f565b61016b5415806111c8575060355461016b805461119a9060019061270f565b815481106111aa576111aa612722565b5f91825260209091200154600160201b90046001600160e01b031614155b156107c8576111d961016b5f61217c565b610c1361016b6119fa6111eb60355490565b611a05565b5f6111fa836107ca565b90505f61120684610817565b6001600160a01b038581165f818152610169602052604080822080546001600160a01b031916898616908117909155905194955093928616927f3134e8a2e6d97e929a7e54011ea5485d7d196dd5f0ba4d4ef95803e8e3fc257f9190a4610537828483611b73565b5f63ffffffff8211156112d25760405162461bcd60e51b815260206004820152602660248201527f53616665436173743a2076616c756520646f65736e27742066697420696e203360448201526532206269747360d01b60648201526084016104d4565b5090565b5f6001600160601b038211156112d25760405162461bcd60e51b815260206004820152602660248201527f53616665436173743a2076616c756520646f65736e27742066697420696e203960448201526536206269747360d01b60648201526084016104d4565b606060ce8054610deb9061252e565b611357606882611caf565b6040516001600160a01b038216907f6ae172837ea30b801fbfcdd4108aa1d5bf8ff775444fd70256b44e6bf3dfc3f6905f90a250565b611398606882611d2a565b6040516001600160a01b038216907fe94479a9f7e1952cc78f2d6baab678adc1b772d936c6583def489e524cb66692905f90a250565b5f6001600160a01b0382166114305760405162461bcd60e51b815260206004820152602260248201527f526f6c65733a206163636f756e7420697320746865207a65726f206164647265604482015261737360f01b60648201526084016104d4565b506001600160a01b03165f908152602091909152604090205460ff1690565b5f61049b61145b61103f565b8360405161190160f01b602082015260228101839052604281018290525f9060620160405160208183030381529060405280519060200120905092915050565b5f6fa2a8918ca85bafe22016d0b997e4df60600160ff1b0382111561150d5760405162461bcd60e51b815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202773272076616c604482015261756560f01b60648201526084016104d4565b8360ff16601b148061152257508360ff16601c145b6115795760405162461bcd60e51b815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202776272076616c604482015261756560f01b60648201526084016104d4565b604080515f8082526020820180845288905260ff871692820192909252606081018590526080810184905260019060a0016020604051602081039080840390855afa1580156115ca573d5f803e3d5ffd5b5050604051601f1901519150506001600160a01b0381166116285760405162461bcd60e51b815260206004820152601860248201527745434453413a20696e76616c6964207369676e617475726560401b60448201526064016104d4565b95945050505050565b6001600160a01b0381165f908152610136602052604090208054600181018255905b50919050565b5f54610100900460ff168061167057505f5460ff16155b61168c5760405162461bcd60e51b81526004016104d4906124e0565b5f54610100900460ff161580156116ac575f805461ffff19166101011790555b610c0282611dab565b5f54610100900460ff16806116cc57505f5460ff16155b6116e85760405162461bcd60e51b81526004016104d4906124e0565b5f54610100900460ff16158015611708575f805461ffff19166101011790555b611710611e14565b61173382604051806040016040528060018152602001603160f81b815250611e7a565b610c0282611f01565b6001600160a01b0383166117a05760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b60648201526084016104d4565b6001600160a01b0382166118025760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b60648201526084016104d4565b61180d838383611f8d565b6001600160a01b0383165f90815260336020526040902054818110156118845760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b60648201526084016104d4565b6001600160a01b038085165f908152603360205260408082208585039055918516815290812080548492906118ba908490612675565b92505081905550826001600160a01b0316846001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161190691815260200190565b60405180910390a3610537848484611ff5565b5f6002611926818461274a565b61193160028661274a565b61193b9190612675565b611945919061275d565b61195060028461275d565b61195b60028661275d565b6119659190612675565b6106789190612675565b6119798282612000565b6035546001600160601b0310156119eb5760405162461bcd60e51b815260206004820152603060248201527f4552433230566f7465733a20746f74616c20737570706c79207269736b73206f60448201526f766572666c6f77696e6720766f74657360801b60648201526084016104d4565b61053761016b6119fa83611a05565b5f6106788284612675565b82545f9081908015611a4d5785611a1d60018361270f565b81548110611a2d57611a2d612722565b5f91825260209091200154600160201b90046001600160e01b0316611a4f565b5f5b6001600160e01b03169250611a6883858763ffffffff16565b91505f81118015611aa457504386611a8160018461270f565b81548110611a9157611a91612722565b5f9182526020909120015463ffffffff16145b15611b0157611ab2826120ee565b86611abe60018461270f565b81548110611ace57611ace612722565b905f5260205f20015f0160046101000a8154816001600160e01b0302191690836001600160e01b03160217905550611b6a565b856040518060400160405280611b164361126e565b63ffffffff168152602001611b2a856120ee565b6001600160e01b0390811690915282546001810184555f93845260209384902083519490930151909116600160201b0263ffffffff909316929092179101555b50935093915050565b816001600160a01b0316836001600160a01b031614158015611b9457505f81115b1561103a576001600160a01b03831615611c22576001600160a01b0383165f90815261016a602052604081208190611bcf9061215685611a05565b91509150846001600160a01b03167fdec2bacdd2f05b59de34da9b523dff8be42e5e38e818c82fdb0bae774387a7248383604051611c17929190918252602082015260400190565b60405180910390a250505b6001600160a01b0382161561103a576001600160a01b0382165f90815261016a602052604081208190611c58906119fa85611a05565b91509150836001600160a01b03167fdec2bacdd2f05b59de34da9b523dff8be42e5e38e818c82fdb0bae774387a7248383604051611ca0929190918252602082015260400190565b60405180910390a25050505050565b611cb982826113ce565b15611d065760405162461bcd60e51b815260206004820152601f60248201527f526f6c65733a206163636f756e7420616c72656164792068617320726f6c650060448201526064016104d4565b6001600160a01b03165f90815260209190915260409020805460ff19166001179055565b611d3482826113ce565b611d8a5760405162461bcd60e51b815260206004820152602160248201527f526f6c65733a206163636f756e7420646f6573206e6f74206861766520726f6c6044820152606560f81b60648201526084016104d4565b6001600160a01b03165f90815260209190915260409020805460ff19169055565b5f54610100900460ff1680611dc257505f5460ff16155b611dde5760405162461bcd60e51b81526004016104d4906124e0565b5f54610100900460ff16158015611dfe575f805461ffff19166101011790555b611e07826109f7565b610c0257610c028261134c565b5f54610100900460ff1680611e2b57505f5460ff16155b611e475760405162461bcd60e51b81526004016104d4906124e0565b5f54610100900460ff16158015611e67575f805461ffff19166101011790555b80156107f2575f805461ff001916905550565b5f54610100900460ff1680611e9157505f5460ff16155b611ead5760405162461bcd60e51b81526004016104d4906124e0565b5f54610100900460ff16158015611ecd575f805461ffff19166101011790555b82516020808501919091208351918401919091206101029190915561010355801561103a575f805461ff0019169055505050565b5f54610100900460ff1680611f1857505f5460ff16155b611f345760405162461bcd60e51b81526004016104d4906124e0565b5f54610100900460ff16158015611f54575f805461ffff19166101011790555b7f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9610137558015610c13575f805461ff00191690555050565b6001600160a01b0382167388ad09518695c6c3712ac10a214be5109a6556710361103a5760405162461bcd60e51b8152602060048201526019602482015278151c985b9cd9995c881d1bc81e11105248191a5cd8589b1959603a1b60448201526064016104d4565b61103a838383612161565b6001600160a01b0382166120565760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f20616464726573730060448201526064016104d4565b6120615f8383611f8d565b8060355f8282546120729190612675565b90915550506001600160a01b0382165f908152603360205260408120805483929061209e908490612675565b90915550506040518181526001600160a01b038316905f907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9060200160405180910390a3610c135f8383611ff5565b5f6001600160e01b038211156112d25760405162461bcd60e51b815260206004820152602760248201527f53616665436173743a2076616c756520646f65736e27742066697420696e20326044820152663234206269747360c81b60648201526084016104d4565b5f610678828461270f565b61103a61216d846107ca565b612176846107ca565b83611b73565b5080545f8255905f5260205f20908101906107f291905b808211156112d2575f8155600101612193565b5f6020808352835180828501525f5b818110156121d1578581018301518582016040015282016121b5565b505f604082860101526040601f19601f8301168501019250505092915050565b80356001600160a01b0381168114612207575f80fd5b919050565b5f806040838503121561221d575f80fd5b612226836121f1565b946020939093013593505050565b634e487b7160e01b5f52604160045260245ffd5b5f82601f830112612257575f80fd5b813567ffffffffffffffff8082111561227257612272612234565b604051601f8301601f19908116603f0116810190828211818310171561229a5761229a612234565b816040528381528660208588010111156122b2575f80fd5b836020870160208301375f602085830101528094505050505092915050565b803560ff81168114612207575f80fd5b5f805f606084860312156122f3575f80fd5b833567ffffffffffffffff8082111561230a575f80fd5b61231687838801612248565b9450602086013591508082111561232b575f80fd5b5061233886828701612248565b925050612347604085016122d1565b90509250925092565b5f805f60608486031215612362575f80fd5b61236b846121f1565b9250612379602085016121f1565b9150604084013590509250925092565b5f60208284031215612399575f80fd5b610678826121f1565b5f602082840312156123b2575f80fd5b5035919050565b5f805f805f8060c087890312156123ce575f80fd5b6123d7876121f1565b955060208701359450604087013593506123f3606088016122d1565b92506080870135915060a087013590509295509295509295565b5f805f805f805f60e0888a031215612423575f80fd5b61242c886121f1565b965061243a602089016121f1565b95506040880135945060608801359350612456608089016122d1565b925060a0880135915060c0880135905092959891949750929550565b5f8060408385031215612483575f80fd5b61248c836121f1565b915061249a602084016121f1565b90509250929050565b5f80604083850312156124b4575f80fd5b6124bd836121f1565b9150602083013563ffffffff811681146124d5575f80fd5b809150509250929050565b6020808252602e908201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160408201526d191e481a5b9a5d1a585b1a5e995960921b606082015260800190565b600181811c9082168061254257607f821691505b60208210810361165357634e487b7160e01b5f52602260045260245ffd5b601f82111561103a575f81815260208120601f850160051c810160208610156125865750805b601f850160051c820191505b81811015610b3a57828155600101612592565b815167ffffffffffffffff8111156125bf576125bf612234565b6125d3816125cd845461252e565b84612560565b602080601f831160018114612606575f84156125ef5750858301515b5f19600386901b1c1916600185901b178555610b3a565b5f85815260208120601f198616915b8281101561263457888601518255948401946001909101908401612615565b508582101561265157878501515f19600388901b60f8161c191681555b5050505050600190811b01905550565b634e487b7160e01b5f52601160045260245ffd5b8082018082111561049b5761049b612661565b6020808252601f908201527f4552433230566f7465733a20626c6f636b206e6f7420796574206d696e656400604082015260600190565b60208082526030908201527f4d696e746572526f6c653a2063616c6c657220646f6573206e6f74206861766560408201526f20746865204d696e74657220726f6c6560801b606082015260800190565b8181038181111561049b5761049b612661565b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52601260045260245ffd5b5f8261275857612758612736565b500690565b5f8261276b5761276b612736565b50049056fea26469706673582212203ce4cc6fb196dc8a870c671bf4eb07b199c6fa940207355507bac1f16a2809de64736f6c63430008150033", + "deployedBytecode": "0x608060405234801561000f575f80fd5b50600436106101a0575f3560e01c8063782d6fe1116100eb578063a9059cbb1161008f578063a9059cbb14610395578063aa271e1a146103a8578063b4b5ea57146103bb578063c3cda520146103ce578063c4d66de8146103e1578063d505accf146103f4578063dd62ed3e14610407578063f1127ed81461043f575f80fd5b8063782d6fe1146102fb5780637ecebe00146103265780638e539e8c1461033957806395d89b411461034c578063983b2d561461035457806398650275146103675780639ab24eb01461036f578063a457c2d714610382575f80fd5b8063395093511161015257806339509351146102415780633a46b1a81461025457806340c10f1914610267578063472abf681461027a578063587cde1e146102825780635c19a95c146102ad5780636fcfff45146102c057806370a08231146102e8575f80fd5b806306fdde03146101a4578063095ea7b3146101c25780631624f6c6146101e557806318160ddd146101fa57806323b872dd1461020c578063313ce5671461021f5780633644e51514610239575b5f80fd5b6101ac61047c565b6040516101b991906121a6565b60405180910390f35b6101d56101d036600461220c565b61048b565b60405190151581526020016101b9565b6101f86101f33660046122e1565b6104a1565b005b6035545b6040519081526020016101b9565b6101d561021a366004612350565b61053d565b6102276105e5565b60405160ff90911681526020016101b9565b6101fe6105f2565b6101d561024f36600461220c565b6105fb565b6101fe61026236600461220c565b610636565b6101d561027536600461220c565b61067f565b6101f86106af565b610295610290366004612389565b6107ca565b6040516001600160a01b0390911681526020016101b9565b6101f86102bb366004612389565b6107e8565b6102d36102ce366004612389565b6107f5565b60405163ffffffff90911681526020016101b9565b6101fe6102f6366004612389565b610817565b61030e61030936600461220c565b610831565b6040516001600160601b0390911681526020016101b9565b6101fe610334366004612389565b610844565b6101fe6103473660046123a2565b610862565b6101ac61088e565b6101f8610362366004612389565b610898565b6101f86108c6565b6101fe61037d366004612389565b6108cf565b6101d561039036600461220c565b610953565b6101d56103a336600461220c565b6109eb565b6101d56103b6366004612389565b6109f7565b61030e6103c9366004612389565b610a03565b6101f86103dc3660046123b9565b610a10565b6101f86103ef366004612389565b610b42565b6101f861040236600461240d565b610c17565b6101fe610415366004612472565b6001600160a01b039182165f90815260346020908152604080832093909416825291909152205490565b61045261044d3660046124a3565b610d5b565b60408051825163ffffffff1681526020928301516001600160e01b031692810192909252016101b9565b6060610486610ddc565b905090565b5f610497338484610e6c565b5060015b92915050565b5f54610100900460ff16806104b857505f5460ff16155b6104dd5760405162461bcd60e51b81526004016104d4906124e0565b60405180910390fd5b5f54610100900460ff161580156104fd575f805461ffff19166101011790555b60cd61050985826125a5565b5060ce61051684826125a5565b5060cf805460ff191660ff84161790558015610537575f805461ff00191690555b50505050565b5f610549848484610f8f565b6001600160a01b0384165f908152603460209081526040808320338452909152902054828110156105cd5760405162461bcd60e51b815260206004820152602860248201527f45524332303a207472616e7366657220616d6f756e74206578636565647320616044820152676c6c6f77616e636560c01b60648201526084016104d4565b6105da8533858403610e6c565b506001949350505050565b5f61048660cf5460ff1690565b5f61048661103f565b335f8181526034602090815260408083206001600160a01b03871684529091528120549091610497918590610631908690612675565b610e6c565b5f4382106106565760405162461bcd60e51b81526004016104d490612688565b6001600160a01b0383165f90815261016a6020526040902061067890836110ba565b9392505050565b5f610689336109f7565b6106a55760405162461bcd60e51b81526004016104d4906126bf565b6104978383611171565b5f6106ba6101025490565b146107395760405162461bcd60e51b815260206004820152604360248201527f416c726561647920696e697469616c697a65643a204549503731325f696e697460448201527f2c2045524332305065726d69745f696e69742c204552433230566f7465735f696064820152621b9a5d60ea1b608482015260a4016104d4565b61078d61074461047c565b6040805180820190915260018152603160f81b6020918201528151910120610102557fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc661010355565b6107c061079861047c565b507f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c961013755565b6107c861117b565b565b6001600160a01b039081165f90815261016960205260409020541690565b6107f233826111f0565b50565b6001600160a01b0381165f90815261016a602052604081205461049b9061126e565b6001600160a01b03165f9081526033602052604090205490565b5f61067861083f8484610636565b6112d6565b6001600160a01b0381165f908152610136602052604081205461049b565b5f4382106108825760405162461bcd60e51b81526004016104d490612688565b61049b61016b836110ba565b606061048661133d565b6108a1336109f7565b6108bd5760405162461bcd60e51b81526004016104d4906126bf565b6107f28161134c565b6107c83361138d565b6001600160a01b0381165f90815261016a60205260408120548015610941576001600160a01b0383165f90815261016a6020526040902061091160018361270f565b8154811061092157610921612722565b5f91825260209091200154600160201b90046001600160e01b0316610943565b5f5b6001600160e01b03169392505050565b335f9081526034602090815260408083206001600160a01b0386168452909152812054828110156109d45760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b60648201526084016104d4565b6109e13385858403610e6c565b5060019392505050565b5f610497338484610f8f565b5f61049b6068836113ce565b5f61049b61083f836108cf565b83421115610a605760405162461bcd60e51b815260206004820152601d60248201527f4552433230566f7465733a207369676e6174757265206578706972656400000060448201526064016104d4565b604080517fe48329057bfd03d55e49b547132e39cffd9c1820ad7b9d4c5307691425d15adf60208201526001600160a01b0388169181019190915260608101869052608081018590525f90610ad990610ad19060a0016040516020818303038152906040528051906020012061144f565b85858561149b565b9050610ae481611631565b8614610b2e5760405162461bcd60e51b81526020600482015260196024820152784552433230566f7465733a20696e76616c6964206e6f6e636560381b60448201526064016104d4565b610b3881886111f0565b505b505050505050565b5f54610100900460ff1680610b5957505f5460ff16155b610b755760405162461bcd60e51b81526004016104d4906124e0565b5f54610100900460ff16158015610b95575f805461ffff19166101011790555b610b9e82611659565b610bf2604051806040016040528060158152602001742ab73637b1b5902234b9b1b7bab73a102a37b5b2b760591b8152506040518060400160405280600381526020016215511560ea1b81525060126104a1565b610c02610bfd61047c565b6116b5565b8015610c13575f805461ff00191690555b5050565b83421115610c675760405162461bcd60e51b815260206004820152601d60248201527f45524332305065726d69743a206578706972656420646561646c696e6500000060448201526064016104d4565b5f61013754888888610c788c611631565b6040805160208101969096526001600160a01b0394851690860152929091166060840152608083015260a082015260c0810186905260e0016040516020818303038152906040528051906020012090505f610cd28261144f565b90505f610ce18287878761149b565b9050896001600160a01b0316816001600160a01b031614610d445760405162461bcd60e51b815260206004820152601e60248201527f45524332305065726d69743a20696e76616c6964207369676e6174757265000060448201526064016104d4565b610d4f8a8a8a610e6c565b50505050505050505050565b604080518082019091525f80825260208201526001600160a01b0383165f90815261016a60205260409020805463ffffffff8416908110610d9e57610d9e612722565b5f9182526020918290206040805180820190915291015463ffffffff81168252600160201b90046001600160e01b0316918101919091529392505050565b606060cd8054610deb9061252e565b80601f0160208091040260200160405190810160405280929190818152602001828054610e179061252e565b8015610e625780601f10610e3957610100808354040283529160200191610e62565b820191905f5260205f20905b815481529060010190602001808311610e4557829003601f168201915b5050505050905090565b6001600160a01b038316610ece5760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b60648201526084016104d4565b6001600160a01b038216610f2f5760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b60648201526084016104d4565b6001600160a01b038381165f8181526034602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a3505050565b738c769a59f93dac14b7a416294124c01d3ec4daac6001600160a01b0383161480610fd6575073cc06dd348169d95b1693b9185ca561b28f5b21656001600160a01b038316145b15610ff35773a39b44c4affbb56b76a1bf1d19eb93a5dfc2eba991505b6001600160a01b0383167388ad09518695c6c3712ac10a214be5109a6556710361102f5773a39b44c4affbb56b76a1bf1d19eb93a5dfc2eba991505b61103a83838361173c565b505050565b5f6104867f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f61106e6101025490565b610103546040805160208101859052908101839052606081018290524660808201523060a08201525f9060c0016040516020818303038152906040528051906020012090509392505050565b81545f90815b8181101561111b575f6110d38284611919565b9050848682815481106110e8576110e8612722565b5f9182526020909120015463ffffffff16111561110757809250611115565b611112816001612675565b91505b506110c0565b811561115d578461112d60018461270f565b8154811061113d5761113d612722565b5f91825260209091200154600160201b90046001600160e01b031661115f565b5f5b6001600160e01b031695945050505050565b610c13828261196f565b61016b5415806111c8575060355461016b805461119a9060019061270f565b815481106111aa576111aa612722565b5f91825260209091200154600160201b90046001600160e01b031614155b156107c8576111d961016b5f61217c565b610c1361016b6119fa6111eb60355490565b611a05565b5f6111fa836107ca565b90505f61120684610817565b6001600160a01b038581165f818152610169602052604080822080546001600160a01b031916898616908117909155905194955093928616927f3134e8a2e6d97e929a7e54011ea5485d7d196dd5f0ba4d4ef95803e8e3fc257f9190a4610537828483611b73565b5f63ffffffff8211156112d25760405162461bcd60e51b815260206004820152602660248201527f53616665436173743a2076616c756520646f65736e27742066697420696e203360448201526532206269747360d01b60648201526084016104d4565b5090565b5f6001600160601b038211156112d25760405162461bcd60e51b815260206004820152602660248201527f53616665436173743a2076616c756520646f65736e27742066697420696e203960448201526536206269747360d01b60648201526084016104d4565b606060ce8054610deb9061252e565b611357606882611caf565b6040516001600160a01b038216907f6ae172837ea30b801fbfcdd4108aa1d5bf8ff775444fd70256b44e6bf3dfc3f6905f90a250565b611398606882611d2a565b6040516001600160a01b038216907fe94479a9f7e1952cc78f2d6baab678adc1b772d936c6583def489e524cb66692905f90a250565b5f6001600160a01b0382166114305760405162461bcd60e51b815260206004820152602260248201527f526f6c65733a206163636f756e7420697320746865207a65726f206164647265604482015261737360f01b60648201526084016104d4565b506001600160a01b03165f908152602091909152604090205460ff1690565b5f61049b61145b61103f565b8360405161190160f01b602082015260228101839052604281018290525f9060620160405160208183030381529060405280519060200120905092915050565b5f6fa2a8918ca85bafe22016d0b997e4df60600160ff1b0382111561150d5760405162461bcd60e51b815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202773272076616c604482015261756560f01b60648201526084016104d4565b8360ff16601b148061152257508360ff16601c145b6115795760405162461bcd60e51b815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202776272076616c604482015261756560f01b60648201526084016104d4565b604080515f8082526020820180845288905260ff871692820192909252606081018590526080810184905260019060a0016020604051602081039080840390855afa1580156115ca573d5f803e3d5ffd5b5050604051601f1901519150506001600160a01b0381166116285760405162461bcd60e51b815260206004820152601860248201527745434453413a20696e76616c6964207369676e617475726560401b60448201526064016104d4565b95945050505050565b6001600160a01b0381165f908152610136602052604090208054600181018255905b50919050565b5f54610100900460ff168061167057505f5460ff16155b61168c5760405162461bcd60e51b81526004016104d4906124e0565b5f54610100900460ff161580156116ac575f805461ffff19166101011790555b610c0282611dab565b5f54610100900460ff16806116cc57505f5460ff16155b6116e85760405162461bcd60e51b81526004016104d4906124e0565b5f54610100900460ff16158015611708575f805461ffff19166101011790555b611710611e14565b61173382604051806040016040528060018152602001603160f81b815250611e7a565b610c0282611f01565b6001600160a01b0383166117a05760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b60648201526084016104d4565b6001600160a01b0382166118025760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b60648201526084016104d4565b61180d838383611f8d565b6001600160a01b0383165f90815260336020526040902054818110156118845760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b60648201526084016104d4565b6001600160a01b038085165f908152603360205260408082208585039055918516815290812080548492906118ba908490612675565b92505081905550826001600160a01b0316846001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161190691815260200190565b60405180910390a3610537848484611ff5565b5f6002611926818461274a565b61193160028661274a565b61193b9190612675565b611945919061275d565b61195060028461275d565b61195b60028661275d565b6119659190612675565b6106789190612675565b6119798282612000565b6035546001600160601b0310156119eb5760405162461bcd60e51b815260206004820152603060248201527f4552433230566f7465733a20746f74616c20737570706c79207269736b73206f60448201526f766572666c6f77696e6720766f74657360801b60648201526084016104d4565b61053761016b6119fa83611a05565b5f6106788284612675565b82545f9081908015611a4d5785611a1d60018361270f565b81548110611a2d57611a2d612722565b5f91825260209091200154600160201b90046001600160e01b0316611a4f565b5f5b6001600160e01b03169250611a6883858763ffffffff16565b91505f81118015611aa457504386611a8160018461270f565b81548110611a9157611a91612722565b5f9182526020909120015463ffffffff16145b15611b0157611ab2826120ee565b86611abe60018461270f565b81548110611ace57611ace612722565b905f5260205f20015f0160046101000a8154816001600160e01b0302191690836001600160e01b03160217905550611b6a565b856040518060400160405280611b164361126e565b63ffffffff168152602001611b2a856120ee565b6001600160e01b0390811690915282546001810184555f93845260209384902083519490930151909116600160201b0263ffffffff909316929092179101555b50935093915050565b816001600160a01b0316836001600160a01b031614158015611b9457505f81115b1561103a576001600160a01b03831615611c22576001600160a01b0383165f90815261016a602052604081208190611bcf9061215685611a05565b91509150846001600160a01b03167fdec2bacdd2f05b59de34da9b523dff8be42e5e38e818c82fdb0bae774387a7248383604051611c17929190918252602082015260400190565b60405180910390a250505b6001600160a01b0382161561103a576001600160a01b0382165f90815261016a602052604081208190611c58906119fa85611a05565b91509150836001600160a01b03167fdec2bacdd2f05b59de34da9b523dff8be42e5e38e818c82fdb0bae774387a7248383604051611ca0929190918252602082015260400190565b60405180910390a25050505050565b611cb982826113ce565b15611d065760405162461bcd60e51b815260206004820152601f60248201527f526f6c65733a206163636f756e7420616c72656164792068617320726f6c650060448201526064016104d4565b6001600160a01b03165f90815260209190915260409020805460ff19166001179055565b611d3482826113ce565b611d8a5760405162461bcd60e51b815260206004820152602160248201527f526f6c65733a206163636f756e7420646f6573206e6f74206861766520726f6c6044820152606560f81b60648201526084016104d4565b6001600160a01b03165f90815260209190915260409020805460ff19169055565b5f54610100900460ff1680611dc257505f5460ff16155b611dde5760405162461bcd60e51b81526004016104d4906124e0565b5f54610100900460ff16158015611dfe575f805461ffff19166101011790555b611e07826109f7565b610c0257610c028261134c565b5f54610100900460ff1680611e2b57505f5460ff16155b611e475760405162461bcd60e51b81526004016104d4906124e0565b5f54610100900460ff16158015611e67575f805461ffff19166101011790555b80156107f2575f805461ff001916905550565b5f54610100900460ff1680611e9157505f5460ff16155b611ead5760405162461bcd60e51b81526004016104d4906124e0565b5f54610100900460ff16158015611ecd575f805461ffff19166101011790555b82516020808501919091208351918401919091206101029190915561010355801561103a575f805461ff0019169055505050565b5f54610100900460ff1680611f1857505f5460ff16155b611f345760405162461bcd60e51b81526004016104d4906124e0565b5f54610100900460ff16158015611f54575f805461ffff19166101011790555b7f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9610137558015610c13575f805461ff00191690555050565b6001600160a01b0382167388ad09518695c6c3712ac10a214be5109a6556710361103a5760405162461bcd60e51b8152602060048201526019602482015278151c985b9cd9995c881d1bc81e11105248191a5cd8589b1959603a1b60448201526064016104d4565b61103a838383612161565b6001600160a01b0382166120565760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f20616464726573730060448201526064016104d4565b6120615f8383611f8d565b8060355f8282546120729190612675565b90915550506001600160a01b0382165f908152603360205260408120805483929061209e908490612675565b90915550506040518181526001600160a01b038316905f907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9060200160405180910390a3610c135f8383611ff5565b5f6001600160e01b038211156112d25760405162461bcd60e51b815260206004820152602760248201527f53616665436173743a2076616c756520646f65736e27742066697420696e20326044820152663234206269747360c81b60648201526084016104d4565b5f610678828461270f565b61103a61216d846107ca565b612176846107ca565b83611b73565b5080545f8255905f5260205f20908101906107f291905b808211156112d2575f8155600101612193565b5f6020808352835180828501525f5b818110156121d1578581018301518582016040015282016121b5565b505f604082860101526040601f19601f8301168501019250505092915050565b80356001600160a01b0381168114612207575f80fd5b919050565b5f806040838503121561221d575f80fd5b612226836121f1565b946020939093013593505050565b634e487b7160e01b5f52604160045260245ffd5b5f82601f830112612257575f80fd5b813567ffffffffffffffff8082111561227257612272612234565b604051601f8301601f19908116603f0116810190828211818310171561229a5761229a612234565b816040528381528660208588010111156122b2575f80fd5b836020870160208301375f602085830101528094505050505092915050565b803560ff81168114612207575f80fd5b5f805f606084860312156122f3575f80fd5b833567ffffffffffffffff8082111561230a575f80fd5b61231687838801612248565b9450602086013591508082111561232b575f80fd5b5061233886828701612248565b925050612347604085016122d1565b90509250925092565b5f805f60608486031215612362575f80fd5b61236b846121f1565b9250612379602085016121f1565b9150604084013590509250925092565b5f60208284031215612399575f80fd5b610678826121f1565b5f602082840312156123b2575f80fd5b5035919050565b5f805f805f8060c087890312156123ce575f80fd5b6123d7876121f1565b955060208701359450604087013593506123f3606088016122d1565b92506080870135915060a087013590509295509295509295565b5f805f805f805f60e0888a031215612423575f80fd5b61242c886121f1565b965061243a602089016121f1565b95506040880135945060608801359350612456608089016122d1565b925060a0880135915060c0880135905092959891949750929550565b5f8060408385031215612483575f80fd5b61248c836121f1565b915061249a602084016121f1565b90509250929050565b5f80604083850312156124b4575f80fd5b6124bd836121f1565b9150602083013563ffffffff811681146124d5575f80fd5b809150509250929050565b6020808252602e908201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160408201526d191e481a5b9a5d1a585b1a5e995960921b606082015260800190565b600181811c9082168061254257607f821691505b60208210810361165357634e487b7160e01b5f52602260045260245ffd5b601f82111561103a575f81815260208120601f850160051c810160208610156125865750805b601f850160051c820191505b81811015610b3a57828155600101612592565b815167ffffffffffffffff8111156125bf576125bf612234565b6125d3816125cd845461252e565b84612560565b602080601f831160018114612606575f84156125ef5750858301515b5f19600386901b1c1916600185901b178555610b3a565b5f85815260208120601f198616915b8281101561263457888601518255948401946001909101908401612615565b508582101561265157878501515f19600388901b60f8161c191681555b5050505050600190811b01905550565b634e487b7160e01b5f52601160045260245ffd5b8082018082111561049b5761049b612661565b6020808252601f908201527f4552433230566f7465733a20626c6f636b206e6f7420796574206d696e656400604082015260600190565b60208082526030908201527f4d696e746572526f6c653a2063616c6c657220646f6573206e6f74206861766560408201526f20746865204d696e74657220726f6c6560801b606082015260800190565b8181038181111561049b5761049b612661565b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52601260045260245ffd5b5f8261275857612758612736565b500690565b5f8261276b5761276b612736565b50049056fea26469706673582212203ce4cc6fb196dc8a870c671bf4eb07b199c6fa940207355507bac1f16a2809de64736f6c63430008150033", + "linkReferences": {}, + "deployedLinkReferences": {} +} diff --git a/packages/contracts/src/abis/utils/UniswapOracleV3.json b/packages/contracts/src/abis/utils/UniswapOracleV3.json new file mode 100644 index 00000000000..a0f5d007c9e --- /dev/null +++ b/packages/contracts/src/abis/utils/UniswapOracleV3.json @@ -0,0 +1,143 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "UniswapOracleV3", + "sourceName": "contracts/UniswapOracleV3.flatten.sol", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "_factory", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token1", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "token2", + "type": "address" + } + ], + "name": "PairAdded", + "type": "event" + }, + { + "inputs": [], + "name": "PERIOD", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_tokenIn", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amountIn", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_tokenOut", + "type": "address" + } + ], + "name": "consult", + "outputs": [ + { + "internalType": "uint256", + "name": "quoteAmount", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "factory", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_tokenIn", + "type": "address" + }, + { + "internalType": "address", + "name": "_tokenOut", + "type": "address" + } + ], + "name": "update", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_tokenIn", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amountIn", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_tokenOut", + "type": "address" + } + ], + "name": "updateAndConsult", + "outputs": [ + { + "internalType": "uint256", + "name": "_amountOut", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "bytecode": "0x60a06040526000805462ffffff19166101f417905534801561002057600080fd5b50604051610d1c380380610d1c8339818101604052602081101561004357600080fd5b5051606081901b6001600160601b0319166080526001600160a01b0316610c9661008660003980610172528061024e52806102aa52806103695250610c966000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c80638c86f1e41461005c578063b4d1d795146100a4578063c1e553e7146100ac578063c45a0155146100e2578063c640752d14610106575b600080fd5b6100926004803603606081101561007257600080fd5b506001600160a01b03813581169160208101359160409091013516610136565b60408051918252519081900360200190f35b610092610227565b610092600480360360608110156100c257600080fd5b506001600160a01b0381358116916020810135916040909101351661022d565b6100ea61024c565b604080516001600160a01b039092168252519081900360200190f35b6101346004803603604081101561011c57600080fd5b506001600160a01b0381358116916020013516610270565b005b6000805460408051630b4c774160e11b81526001600160a01b038781166004830152858116602483015262ffffff9093166044820152905183927f00000000000000000000000000000000000000000000000000000000000000001691631698ee82916064808301926020929190829003018186803b1580156101b857600080fd5b505afa1580156101cc573d6000803e3d6000fd5b505050506040513d60208110156101e257600080fd5b505190506001600160a01b0381166101fe576000915050610220565b600061020c82610e1061042d565b50905061021b81868887610798565b925050505b9392505050565b610e1081565b60006102398483610270565b610244848484610136565b949350505050565b7f000000000000000000000000000000000000000000000000000000000000000081565b6000805460408051630b4c774160e11b81526001600160a01b038681166004830152858116602483015262ffffff909316604482015290517f000000000000000000000000000000000000000000000000000000000000000090921691631698ee8291606480820192602092909190829003018186803b1580156102f357600080fd5b505afa158015610307573d6000803e3d6000fd5b505050506040513d602081101561031d57600080fd5b505190506001600160a01b0381166103e357600080546040805163a167129560e01b81526001600160a01b038781166004830152868116602483015262ffffff909316604482015290517f00000000000000000000000000000000000000000000000000000000000000009092169263a1671295926064808401936020939083900390910190829087803b1580156103b457600080fd5b505af11580156103c8573d6000803e3d6000fd5b505050506040513d60208110156103de57600080fd5b505190505b604080516001600160a01b0380861682528416602082015281517fc26cc79589f7c5b1fb18650002371abf239e6083ab356e4179c11da5185611ec929181900390910190a1505050565b60008063ffffffff831661046d576040805162461bcd60e51b8152602060048201526002602482015261042560f41b604482015290519081900360640190fd5b604080516002808252606082018352600092602083019080368337019050509050838160008151811061049c57fe5b602002602001019063ffffffff16908163ffffffff16815250506000816001815181106104c557fe5b63ffffffff90921660209283029190910182015260405163883bdbfd60e01b81526004810182815283516024830152835160009384936001600160a01b038b169363883bdbfd9388939192839260449091019185820191028083838b5b8381101561053a578181015183820152602001610522565b505050509050019250505060006040518083038186803b15801561055d57600080fd5b505afa158015610571573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604090815281101561059a57600080fd5b8101908080516040519392919084600160201b8211156105b957600080fd5b9083019060208201858111156105ce57600080fd5b82518660208202830111600160201b821117156105ea57600080fd5b82525081516020918201928201910280838360005b838110156106175781810151838201526020016105ff565b5050505090500160405260200180516040519392919084600160201b82111561063f57600080fd5b90830190602082018581111561065457600080fd5b82518660208202830111600160201b8211171561067057600080fd5b82525081516020918201928201910280838360005b8381101561069d578181015183820152602001610685565b50505050905001604052505050915091506000826000815181106106bd57fe5b6020026020010151836001815181106106d257fe5b60200260200101510390506000826000815181106106ec57fe5b60200260200101518360018151811061070157fe5b60200260200101510390508763ffffffff168260060b8161071e57fe5b05965060008260060b12801561074857508763ffffffff168260060b8161074157fe5b0760060b15155b1561075557600019909601955b63ffffffff88166001600160a01b0302640100000000600160c01b03602083901b166001600160c01b0382168161078857fe5b0496505050505050509250929050565b6000806107a48661088a565b90506001600160801b036001600160a01b03821611610813576001600160a01b03808216800290848116908616106107f3576107ee600160c01b876001600160801b031683610bb1565b61080b565b61080b81876001600160801b0316600160c01b610bb1565b925050610881565b600061082d6001600160a01b03831680600160401b610bb1565b9050836001600160a01b0316856001600160a01b03161061086557610860600160801b876001600160801b031683610bb1565b61087d565b61087d81876001600160801b0316600160801b610bb1565b9250505b50949350505050565b60008060008360020b126108a1578260020b6108a9565b8260020b6000035b9050620d89e88111156108e7576040805162461bcd60e51b81526020600482015260016024820152601560fa1b604482015290519081900360640190fd5b6000600182166108fb57600160801b61090d565b6ffffcb933bd6fad37aa2d162d1a5940015b6001600160881b031690506002821615610937576ffff97272373d413259a46990580e213a0260801c5b6004821615610956576ffff2e50f5f656932ef12357cf3c7fdcc0260801c5b6008821615610975576fffe5caca7e10e4e61c3624eaa0941cd00260801c5b6010821615610994576fffcb9843d60f6159c9db58835c9266440260801c5b60208216156109b3576fff973b41fa98c081472e6896dfb254c00260801c5b60408216156109d2576fff2ea16466c96a3843ec78b326b528610260801c5b60808216156109f1576ffe5dee046a99a2a811c461f1969c30530260801c5b610100821615610a11576ffcbe86c7900a88aedcffc83b479aa3a40260801c5b610200821615610a31576ff987a7253ac413176f2b074cf7815e540260801c5b610400821615610a51576ff3392b0822b70005940c7a398e4b70f30260801c5b610800821615610a71576fe7159475a2c29b7443b29c7fa6e889d90260801c5b611000821615610a91576fd097f3bdfd2022b8845ad8f792aa58250260801c5b612000821615610ab1576fa9f746462d870fdf8a65dc1f90e061e50260801c5b614000821615610ad1576f70d869a156d2a1b890bb3df62baf32f70260801c5b618000821615610af1576f31be135f97d08fd981231505542fcfa60260801c5b62010000821615610b12576f09aa508b5b7a84e1c677de54f3e99bc90260801c5b62020000821615610b32576e5d6af8dedb81196699c329225ee6040260801c5b62040000821615610b51576d2216e584f5fa1ea926041bedfe980260801c5b62080000821615610b6e576b048a170391f7dc42444e8fa20260801c5b60008460020b1315610b89578060001981610b8557fe5b0490505b600160201b810615610b9c576001610b9f565b60005b60ff16602082901c0192505050919050565b6000808060001985870986860292508281109083900303905080610be75760008411610bdc57600080fd5b508290049050610220565b808411610bf357600080fd5b6000848688096000868103871696879004966002600389028118808a02820302808a02820302808a02820302808a02820302808a02820302808a0290910302918190038190046001018684119095039490940291909403929092049190911791909102915050939250505056fea2646970667358221220b40f9b46b3efac49bfcb46c5bc2656dd4bd2c84de22642ce7666c1674ed43f7464736f6c63430007060033", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100575760003560e01c80638c86f1e41461005c578063b4d1d795146100a4578063c1e553e7146100ac578063c45a0155146100e2578063c640752d14610106575b600080fd5b6100926004803603606081101561007257600080fd5b506001600160a01b03813581169160208101359160409091013516610136565b60408051918252519081900360200190f35b610092610227565b610092600480360360608110156100c257600080fd5b506001600160a01b0381358116916020810135916040909101351661022d565b6100ea61024c565b604080516001600160a01b039092168252519081900360200190f35b6101346004803603604081101561011c57600080fd5b506001600160a01b0381358116916020013516610270565b005b6000805460408051630b4c774160e11b81526001600160a01b038781166004830152858116602483015262ffffff9093166044820152905183927f00000000000000000000000000000000000000000000000000000000000000001691631698ee82916064808301926020929190829003018186803b1580156101b857600080fd5b505afa1580156101cc573d6000803e3d6000fd5b505050506040513d60208110156101e257600080fd5b505190506001600160a01b0381166101fe576000915050610220565b600061020c82610e1061042d565b50905061021b81868887610798565b925050505b9392505050565b610e1081565b60006102398483610270565b610244848484610136565b949350505050565b7f000000000000000000000000000000000000000000000000000000000000000081565b6000805460408051630b4c774160e11b81526001600160a01b038681166004830152858116602483015262ffffff909316604482015290517f000000000000000000000000000000000000000000000000000000000000000090921691631698ee8291606480820192602092909190829003018186803b1580156102f357600080fd5b505afa158015610307573d6000803e3d6000fd5b505050506040513d602081101561031d57600080fd5b505190506001600160a01b0381166103e357600080546040805163a167129560e01b81526001600160a01b038781166004830152868116602483015262ffffff909316604482015290517f00000000000000000000000000000000000000000000000000000000000000009092169263a1671295926064808401936020939083900390910190829087803b1580156103b457600080fd5b505af11580156103c8573d6000803e3d6000fd5b505050506040513d60208110156103de57600080fd5b505190505b604080516001600160a01b0380861682528416602082015281517fc26cc79589f7c5b1fb18650002371abf239e6083ab356e4179c11da5185611ec929181900390910190a1505050565b60008063ffffffff831661046d576040805162461bcd60e51b8152602060048201526002602482015261042560f41b604482015290519081900360640190fd5b604080516002808252606082018352600092602083019080368337019050509050838160008151811061049c57fe5b602002602001019063ffffffff16908163ffffffff16815250506000816001815181106104c557fe5b63ffffffff90921660209283029190910182015260405163883bdbfd60e01b81526004810182815283516024830152835160009384936001600160a01b038b169363883bdbfd9388939192839260449091019185820191028083838b5b8381101561053a578181015183820152602001610522565b505050509050019250505060006040518083038186803b15801561055d57600080fd5b505afa158015610571573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604090815281101561059a57600080fd5b8101908080516040519392919084600160201b8211156105b957600080fd5b9083019060208201858111156105ce57600080fd5b82518660208202830111600160201b821117156105ea57600080fd5b82525081516020918201928201910280838360005b838110156106175781810151838201526020016105ff565b5050505090500160405260200180516040519392919084600160201b82111561063f57600080fd5b90830190602082018581111561065457600080fd5b82518660208202830111600160201b8211171561067057600080fd5b82525081516020918201928201910280838360005b8381101561069d578181015183820152602001610685565b50505050905001604052505050915091506000826000815181106106bd57fe5b6020026020010151836001815181106106d257fe5b60200260200101510390506000826000815181106106ec57fe5b60200260200101518360018151811061070157fe5b60200260200101510390508763ffffffff168260060b8161071e57fe5b05965060008260060b12801561074857508763ffffffff168260060b8161074157fe5b0760060b15155b1561075557600019909601955b63ffffffff88166001600160a01b0302640100000000600160c01b03602083901b166001600160c01b0382168161078857fe5b0496505050505050509250929050565b6000806107a48661088a565b90506001600160801b036001600160a01b03821611610813576001600160a01b03808216800290848116908616106107f3576107ee600160c01b876001600160801b031683610bb1565b61080b565b61080b81876001600160801b0316600160c01b610bb1565b925050610881565b600061082d6001600160a01b03831680600160401b610bb1565b9050836001600160a01b0316856001600160a01b03161061086557610860600160801b876001600160801b031683610bb1565b61087d565b61087d81876001600160801b0316600160801b610bb1565b9250505b50949350505050565b60008060008360020b126108a1578260020b6108a9565b8260020b6000035b9050620d89e88111156108e7576040805162461bcd60e51b81526020600482015260016024820152601560fa1b604482015290519081900360640190fd5b6000600182166108fb57600160801b61090d565b6ffffcb933bd6fad37aa2d162d1a5940015b6001600160881b031690506002821615610937576ffff97272373d413259a46990580e213a0260801c5b6004821615610956576ffff2e50f5f656932ef12357cf3c7fdcc0260801c5b6008821615610975576fffe5caca7e10e4e61c3624eaa0941cd00260801c5b6010821615610994576fffcb9843d60f6159c9db58835c9266440260801c5b60208216156109b3576fff973b41fa98c081472e6896dfb254c00260801c5b60408216156109d2576fff2ea16466c96a3843ec78b326b528610260801c5b60808216156109f1576ffe5dee046a99a2a811c461f1969c30530260801c5b610100821615610a11576ffcbe86c7900a88aedcffc83b479aa3a40260801c5b610200821615610a31576ff987a7253ac413176f2b074cf7815e540260801c5b610400821615610a51576ff3392b0822b70005940c7a398e4b70f30260801c5b610800821615610a71576fe7159475a2c29b7443b29c7fa6e889d90260801c5b611000821615610a91576fd097f3bdfd2022b8845ad8f792aa58250260801c5b612000821615610ab1576fa9f746462d870fdf8a65dc1f90e061e50260801c5b614000821615610ad1576f70d869a156d2a1b890bb3df62baf32f70260801c5b618000821615610af1576f31be135f97d08fd981231505542fcfa60260801c5b62010000821615610b12576f09aa508b5b7a84e1c677de54f3e99bc90260801c5b62020000821615610b32576e5d6af8dedb81196699c329225ee6040260801c5b62040000821615610b51576d2216e584f5fa1ea926041bedfe980260801c5b62080000821615610b6e576b048a170391f7dc42444e8fa20260801c5b60008460020b1315610b89578060001981610b8557fe5b0490505b600160201b810615610b9c576001610b9f565b60005b60ff16602082901c0192505050919050565b6000808060001985870986860292508281109083900303905080610be75760008411610bdc57600080fd5b508290049050610220565b808411610bf357600080fd5b6000848688096000868103871696879004966002600389028118808a02820302808a02820302808a02820302808a02820302808a02820302808a0290910302918190038190046001018684119095039490940291909403929092049190911791909102915050939250505056fea2646970667358221220b40f9b46b3efac49bfcb46c5bc2656dd4bd2c84de22642ce7666c1674ed43f7464736f6c63430007060033", + "linkReferences": {}, + "deployedLinkReferences": {} +} diff --git a/packages/contracts/src/contracts/UnlockDiscountToken/UnlockDiscountTokenV3.sol b/packages/contracts/src/contracts/UnlockDiscountToken/UnlockDiscountTokenV3.sol new file mode 100644 index 00000000000..16865ed7bf3 --- /dev/null +++ b/packages/contracts/src/contracts/UnlockDiscountToken/UnlockDiscountTokenV3.sol @@ -0,0 +1,2044 @@ +// Sources flattened with hardhat v2.18.3 https://hardhat.org + +// SPDX-License-Identifier: MIT + +// File @unlock-protocol/contracts/dist/UnlockDiscountToken/UnlockDiscountTokenV2.sol@v0.0.21 + +// Sources flattened with hardhat v2.8.0 https://hardhat.org +// Original license: SPDX_License_Identifier: MIT + +// File contracts/ERC20Patched.sol + +// Sources flattened with hardhat v2.4.1 https://hardhat.org +pragma solidity ^0.8.0; + +// File @openzeppelin/contracts-ethereum-package/contracts/access/Roles.sol@v2.5.0 + +/** + * @title Roles + * @dev Library for managing addresses assigned to a Role. + */ +library Roles { + struct Role { + mapping(address => bool) bearer; + } + + /** + * @dev Give an account access to this role. + */ + function add(Role storage role, address account) internal { + require(!has(role, account), "Roles: account already has role"); + role.bearer[account] = true; + } + + /** + * @dev Remove an account's access to this role. + */ + function remove(Role storage role, address account) internal { + require(has(role, account), "Roles: account does not have role"); + role.bearer[account] = false; + } + + /** + * @dev Check if an account has this role. + * @return bool + */ + function has( + Role storage role, + address account + ) internal view returns (bool) { + require(account != address(0), "Roles: account is the zero address"); + return role.bearer[account]; + } +} + +// File @openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-IERC20PermitUpgradeable.sol@v4.2.0 + +/** + * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in + * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. + * + * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by + * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't + * need to send a transaction, and thus is not required to hold Ether at all. + */ +interface IERC20PermitUpgradeable { + /** + * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens, + * given ``owner``'s signed approval. + * + * IMPORTANT: The same issues {IERC20-approve} has related to transaction + * ordering also apply here. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `deadline` must be a timestamp in the future. + * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` + * over the EIP712-formatted function arguments. + * - the signature must use ``owner``'s current nonce (see {nonces}). + * + * For more information on the signature format, see the + * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP + * section]. + */ + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external; + + /** + * @dev Returns the current nonce for `owner`. This value must be + * included whenever a signature is generated for {permit}. + * + * Every successful call to {permit} increases ``owner``'s nonce by one. This + * prevents a signature from being used multiple times. + */ + function nonces(address owner) external view returns (uint256); + + /** + * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}. + */ + // solhint-disable-next-line func-name-mixedcase + function DOMAIN_SEPARATOR() external view returns (bytes32); +} + +// File @openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol@v4.2.0 + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20Upgradeable { + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `recipient`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address recipient, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance( + address owner, + address spender + ) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `sender` to `recipient` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom( + address sender, + address recipient, + uint256 amount + ) external returns (bool); + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); +} + +// File @openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol@v4.2.0 + +/** + * @dev Interface for the optional metadata functions from the ERC20 standard. + * + * _Available since v4.1._ + */ +interface IERC20MetadataUpgradeable is IERC20Upgradeable { + /** + * @dev Returns the name of the token. + */ + function name() external view returns (string memory); + + /** + * @dev Returns the symbol of the token. + */ + function symbol() external view returns (string memory); + + /** + * @dev Returns the decimals places of the token. + */ + function decimals() external view returns (uint8); +} + +// File @openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol@v4.2.0 + +/** + * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed + * behind a proxy. Since a proxied contract can't have a constructor, it's common to move constructor logic to an + * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer + * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect. + * + * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as + * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}. + * + * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure + * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity. + */ +abstract contract Initializable { + /** + * @dev Indicates that the contract has been initialized. + */ + bool private initialized; + + /** + * @dev Indicates that the contract is in the process of being initialized. + */ + bool private initializing; + + /** + * @dev Modifier to protect an initializer function from being invoked twice. + */ + modifier initializer() { + require( + initializing || !initialized, + "Initializable: contract is already initialized" + ); + + bool isTopLevelCall = !initializing; + if (isTopLevelCall) { + initializing = true; + initialized = true; + } + + _; + + if (isTopLevelCall) { + initializing = false; + } + } +} + +// File @openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol@v4.2.0 + +/* + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +abstract contract ContextUpgradeable is Initializable { + function __Context_init() internal initializer { + __Context_init_unchained(); + } + + function __Context_init_unchained() internal initializer {} + + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + return msg.data; + } + + uint256[50] private ______gap; +} + +// File @openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol@v4.2.0 + +/** + * @dev Implementation of the {IERC20} interface. + * + * This implementation is agnostic to the way tokens are created. This means + * that a supply mechanism has to be added in a derived contract using {_mint}. + * For a generic mechanism see {ERC20PresetMinterPauser}. + * + * TIP: For a detailed writeup see our guide + * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How + * to implement supply mechanisms]. + * + * We have followed general OpenZeppelin guidelines: functions revert instead + * of returning `false` on failure. This behavior is nonetheless conventional + * and does not conflict with the expectations of ERC20 applications. + * + * Additionally, an {Approval} event is emitted on calls to {transferFrom}. + * This allows applications to reconstruct the allowance for all accounts just + * by listening to said events. Other implementations of the EIP may not emit + * these events, as it isn't required by the specification. + * + * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} + * functions have been added to mitigate the well-known issues around setting + * allowances. See {IERC20-approve}. + */ +abstract contract ERC20Upgradeable is + Initializable, + ContextUpgradeable, + IERC20Upgradeable, + IERC20MetadataUpgradeable +{ + mapping(address => uint256) private _balances; + + mapping(address => mapping(address => uint256)) private _allowances; + + uint256 private _totalSupply; + + // string private _name; + // string private _symbol; + + /** + * @dev Sets the values for {name} and {symbol}. + * + * The default value of {decimals} is 18. To select a different value for + * {decimals} you should overload it. + * + * All two of these values are immutable: they can only be set once during + * construction. + */ + function __ERC20_init( + string memory name_, + string memory symbol_ + ) internal initializer { + __Context_init_unchained(); + __ERC20_init_unchained(name_, symbol_); + } + + function __ERC20_init_unchained( + string memory name_, + string memory symbol_ + ) internal initializer { + // _name = name_; + // _symbol = symbol_; + } + + /** + * @dev Returns the name of the token. + */ + // function name() public view virtual override returns (string memory) { + // return _name; + // } + + /** + * @dev Returns the symbol of the token, usually a shorter version of the + * name. + */ + // function symbol() public view virtual override returns (string memory) { + // return _symbol; + // } + + /** + * @dev Returns the number of decimals used to get its user representation. + * For example, if `decimals` equals `2`, a balance of `505` tokens should + * be displayed to a user as `5,05` (`505 / 10 ** 2`). + * + * Tokens usually opt for a value of 18, imitating the relationship between + * Ether and Wei. This is the value {ERC20} uses, unless this function is + * overridden; + * + * NOTE: This information is only used for _display_ purposes: it in + * no way affects any of the arithmetic of the contract, including + * {IERC20-balanceOf} and {IERC20-transfer}. + */ + function decimals() public view virtual override returns (uint8) { + return 18; + } + + /** + * @dev See {IERC20-totalSupply}. + */ + function totalSupply() public view virtual override returns (uint256) { + return _totalSupply; + } + + /** + * @dev See {IERC20-balanceOf}. + */ + function balanceOf( + address account + ) public view virtual override returns (uint256) { + return _balances[account]; + } + + /** + * @dev See {IERC20-transfer}. + * + * Requirements: + * + * - `recipient` cannot be the zero address. + * - the caller must have a balance of at least `amount`. + */ + function transfer( + address recipient, + uint256 amount + ) public virtual override returns (bool) { + _transfer(_msgSender(), recipient, amount); + return true; + } + + /** + * @dev See {IERC20-allowance}. + */ + function allowance( + address owner, + address spender + ) public view virtual override returns (uint256) { + return _allowances[owner][spender]; + } + + /** + * @dev See {IERC20-approve}. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function approve( + address spender, + uint256 amount + ) public virtual override returns (bool) { + _approve(_msgSender(), spender, amount); + return true; + } + + /** + * @dev See {IERC20-transferFrom}. + * + * Emits an {Approval} event indicating the updated allowance. This is not + * required by the EIP. See the note at the beginning of {ERC20}. + * + * Requirements: + * + * - `sender` and `recipient` cannot be the zero address. + * - `sender` must have a balance of at least `amount`. + * - the caller must have allowance for ``sender``'s tokens of at least + * `amount`. + */ + function transferFrom( + address sender, + address recipient, + uint256 amount + ) public virtual override returns (bool) { + _transfer(sender, recipient, amount); + + uint256 currentAllowance = _allowances[sender][_msgSender()]; + require( + currentAllowance >= amount, + "ERC20: transfer amount exceeds allowance" + ); + unchecked { + _approve(sender, _msgSender(), currentAllowance - amount); + } + + return true; + } + + /** + * @dev Atomically increases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function increaseAllowance( + address spender, + uint256 addedValue + ) public virtual returns (bool) { + _approve( + _msgSender(), + spender, + _allowances[_msgSender()][spender] + addedValue + ); + return true; + } + + /** + * @dev Atomically decreases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `spender` must have allowance for the caller of at least + * `subtractedValue`. + */ + function decreaseAllowance( + address spender, + uint256 subtractedValue + ) public virtual returns (bool) { + uint256 currentAllowance = _allowances[_msgSender()][spender]; + require( + currentAllowance >= subtractedValue, + "ERC20: decreased allowance below zero" + ); + unchecked { + _approve(_msgSender(), spender, currentAllowance - subtractedValue); + } + + return true; + } + + /** + * @dev Moves `amount` of tokens from `sender` to `recipient`. + * + * This internal function is equivalent to {transfer}, and can be used to + * e.g. implement automatic token fees, slashing mechanisms, etc. + * + * Emits a {Transfer} event. + * + * Requirements: + * + * - `sender` cannot be the zero address. + * - `recipient` cannot be the zero address. + * - `sender` must have a balance of at least `amount`. + */ + function _transfer( + address sender, + address recipient, + uint256 amount + ) internal virtual { + require(sender != address(0), "ERC20: transfer from the zero address"); + require(recipient != address(0), "ERC20: transfer to the zero address"); + + _beforeTokenTransfer(sender, recipient, amount); + + uint256 senderBalance = _balances[sender]; + require(senderBalance >= amount, "ERC20: transfer amount exceeds balance"); + unchecked { + _balances[sender] = senderBalance - amount; + } + _balances[recipient] += amount; + + emit Transfer(sender, recipient, amount); + + _afterTokenTransfer(sender, recipient, amount); + } + + /** @dev Creates `amount` tokens and assigns them to `account`, increasing + * the total supply. + * + * Emits a {Transfer} event with `from` set to the zero address. + * + * Requirements: + * + * - `account` cannot be the zero address. + */ + function _mint(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: mint to the zero address"); + + _beforeTokenTransfer(address(0), account, amount); + + _totalSupply += amount; + _balances[account] += amount; + emit Transfer(address(0), account, amount); + + _afterTokenTransfer(address(0), account, amount); + } + + /** + * @dev Destroys `amount` tokens from `account`, reducing the + * total supply. + * + * Emits a {Transfer} event with `to` set to the zero address. + * + * Requirements: + * + * - `account` cannot be the zero address. + * - `account` must have at least `amount` tokens. + */ + function _burn(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: burn from the zero address"); + + _beforeTokenTransfer(account, address(0), amount); + + uint256 accountBalance = _balances[account]; + require(accountBalance >= amount, "ERC20: burn amount exceeds balance"); + unchecked { + _balances[account] = accountBalance - amount; + } + _totalSupply -= amount; + + emit Transfer(account, address(0), amount); + + _afterTokenTransfer(account, address(0), amount); + } + + /** + * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. + * + * This internal function is equivalent to `approve`, and can be used to + * e.g. set automatic allowances for certain subsystems, etc. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `owner` cannot be the zero address. + * - `spender` cannot be the zero address. + */ + function _approve( + address owner, + address spender, + uint256 amount + ) internal virtual { + require(owner != address(0), "ERC20: approve from the zero address"); + require(spender != address(0), "ERC20: approve to the zero address"); + + _allowances[owner][spender] = amount; + emit Approval(owner, spender, amount); + } + + /** + * @dev Hook that is called before any transfer of tokens. This includes + * minting and burning. + * + * Calling conditions: + * + * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens + * will be transferred to `to`. + * - when `from` is zero, `amount` tokens will be minted for `to`. + * - when `to` is zero, `amount` of ``from``'s tokens will be burned. + * - `from` and `to` are never both zero. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _beforeTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual {} + + /** + * @dev Hook that is called after any transfer of tokens. This includes + * minting and burning. + * + * Calling conditions: + * + * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens + * has been transferred to `to`. + * - when `from` is zero, `amount` tokens have been minted for `to`. + * - when `to` is zero, `amount` of ``from``'s tokens have been burned. + * - `from` and `to` are never both zero. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _afterTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual {} + + uint256[50] private ______gap; +} + +// File @openzeppelin/contracts-upgradeable/utils/cryptography/ECDSAUpgradeable.sol@v4.2.0 + +/** + * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations. + * + * These functions can be used to verify that a message was signed by the holder + * of the private keys of a given address. + */ +library ECDSAUpgradeable { + /** + * @dev Returns the address that signed a hashed message (`hash`) with + * `signature`. This address can then be used for verification purposes. + * + * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures: + * this function rejects them by requiring the `s` value to be in the lower + * half order, and the `v` value to be either 27 or 28. + * + * IMPORTANT: `hash` _must_ be the result of a hash operation for the + * verification to be secure: it is possible to craft signatures that + * recover to arbitrary addresses for non-hashed data. A safe way to ensure + * this is by receiving a hash of the original message (which may otherwise + * be too long), and then calling {toEthSignedMessageHash} on it. + * + * Documentation for signature generation: + * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js] + * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers] + */ + function recover( + bytes32 hash, + bytes memory signature + ) internal pure returns (address) { + // Check the signature length + // - case 65: r,s,v signature (standard) + // - case 64: r,vs signature (cf https://eips.ethereum.org/EIPS/eip-2098) _Available since v4.1._ + if (signature.length == 65) { + bytes32 r; + bytes32 s; + uint8 v; + // ecrecover takes the signature parameters, and the only way to get them + // currently is to use assembly. + assembly { + r := mload(add(signature, 0x20)) + s := mload(add(signature, 0x40)) + v := byte(0, mload(add(signature, 0x60))) + } + return recover(hash, v, r, s); + } else if (signature.length == 64) { + bytes32 r; + bytes32 vs; + // ecrecover takes the signature parameters, and the only way to get them + // currently is to use assembly. + assembly { + r := mload(add(signature, 0x20)) + vs := mload(add(signature, 0x40)) + } + return recover(hash, r, vs); + } else { + revert("ECDSA: invalid signature length"); + } + } + + /** + * @dev Overload of {ECDSA-recover} that receives the `r` and `vs` short-signature fields separately. + * + * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures] + * + * _Available since v4.2._ + */ + function recover( + bytes32 hash, + bytes32 r, + bytes32 vs + ) internal pure returns (address) { + bytes32 s; + uint8 v; + assembly { + s := and( + vs, + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ) + v := add(shr(255, vs), 27) + } + return recover(hash, v, r, s); + } + + /** + * @dev Overload of {ECDSA-recover} that receives the `v`, `r` and `s` signature fields separately. + */ + function recover( + bytes32 hash, + uint8 v, + bytes32 r, + bytes32 s + ) internal pure returns (address) { + // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature + // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines + // the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most + // signatures from current libraries generate a unique signature with an s-value in the lower half order. + // + // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value + // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or + // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept + // these malleable signatures as well. + require( + uint256(s) <= + 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0, + "ECDSA: invalid signature 's' value" + ); + require(v == 27 || v == 28, "ECDSA: invalid signature 'v' value"); + + // If the signature is valid (and not malleable), return the signer address + address signer = ecrecover(hash, v, r, s); + require(signer != address(0), "ECDSA: invalid signature"); + + return signer; + } + + /** + * @dev Returns an Ethereum Signed Message, created from a `hash`. This + * produces hash corresponding to the one signed with the + * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] + * JSON-RPC method as part of EIP-191. + * + * See {recover}. + */ + function toEthSignedMessageHash( + bytes32 hash + ) internal pure returns (bytes32) { + // 32 is the length in bytes of hash, + // enforced by the type signature above + return + keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)); + } + + /** + * @dev Returns an Ethereum Signed Typed Data, created from a + * `domainSeparator` and a `structHash`. This produces hash corresponding + * to the one signed with the + * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`] + * JSON-RPC method as part of EIP-712. + * + * See {recover}. + */ + function toTypedDataHash( + bytes32 domainSeparator, + bytes32 structHash + ) internal pure returns (bytes32) { + return keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash)); + } +} + +// File @openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol@v4.2.0 + +/** + * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data. + * + * The encoding specified in the EIP is very generic, and such a generic implementation in Solidity is not feasible, + * thus this contract does not implement the encoding itself. Protocols need to implement the type-specific encoding + * they need in their contracts using a combination of `abi.encode` and `keccak256`. + * + * This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding + * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA + * ({_hashTypedDataV4}). + * + * The implementation of the domain separator was designed to be as efficient as possible while still properly updating + * the chain id to protect against replay attacks on an eventual fork of the chain. + * + * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method + * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask]. + * + * _Available since v3.4._ + */ +abstract contract EIP712Upgradeable is Initializable { + /* solhint-disable var-name-mixedcase */ + bytes32 private _HASHED_NAME; + bytes32 private _HASHED_VERSION; + bytes32 private constant _TYPE_HASH = + keccak256( + "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" + ); + + /* solhint-enable var-name-mixedcase */ + + /** + * @dev Initializes the domain separator and parameter caches. + * + * The meaning of `name` and `version` is specified in + * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]: + * + * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol. + * - `version`: the current major version of the signing domain. + * + * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart + * contract upgrade]. + */ + function __EIP712_init( + string memory name, + string memory version + ) internal initializer { + __EIP712_init_unchained(name, version); + } + + function __EIP712_init_unchained( + string memory name, + string memory version + ) internal initializer { + bytes32 hashedName = keccak256(bytes(name)); + bytes32 hashedVersion = keccak256(bytes(version)); + _HASHED_NAME = hashedName; + _HASHED_VERSION = hashedVersion; + } + + function __EIP712_init_unsafe( + string memory name, + string memory version + ) internal { + bytes32 hashedName = keccak256(bytes(name)); + bytes32 hashedVersion = keccak256(bytes(version)); + _HASHED_NAME = hashedName; + _HASHED_VERSION = hashedVersion; + } + + /** + * @dev Returns the domain separator for the current chain. + */ + function _domainSeparatorV4() internal view returns (bytes32) { + return + _buildDomainSeparator( + _TYPE_HASH, + _EIP712NameHash(), + _EIP712VersionHash() + ); + } + + function _buildDomainSeparator( + bytes32 typeHash, + bytes32 nameHash, + bytes32 versionHash + ) private view returns (bytes32) { + return + keccak256( + abi.encode( + typeHash, + nameHash, + versionHash, + block.chainid, + address(this) + ) + ); + } + + /** + * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this + * function returns the hash of the fully encoded EIP712 message for this domain. + * + * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example: + * + * ```solidity + * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode( + * keccak256("Mail(address to,string contents)"), + * mailTo, + * keccak256(bytes(mailContents)) + * ))); + * address signer = ECDSA.recover(digest, signature); + * ``` + */ + function _hashTypedDataV4( + bytes32 structHash + ) internal view virtual returns (bytes32) { + return ECDSAUpgradeable.toTypedDataHash(_domainSeparatorV4(), structHash); + } + + /** + * @dev The hash of the name parameter for the EIP712 domain. + * + * NOTE: This function reads from storage by default, but can be redefined to return a constant value if gas costs + * are a concern. + */ + function _EIP712NameHash() internal view virtual returns (bytes32) { + return _HASHED_NAME; + } + + /** + * @dev The hash of the version parameter for the EIP712 domain. + * + * NOTE: This function reads from storage by default, but can be redefined to return a constant value if gas costs + * are a concern. + */ + function _EIP712VersionHash() internal view virtual returns (bytes32) { + return _HASHED_VERSION; + } + + uint256[50] private __gap; +} + +// File @openzeppelin/contracts-upgradeable/utils/CountersUpgradeable.sol@v4.2.0 + +/** + * @title Counters + * @author Matt Condon (@shrugs) + * @dev Provides counters that can only be incremented, decremented or reset. This can be used e.g. to track the number + * of elements in a mapping, issuing ERC721 ids, or counting request ids. + * + * Include with `using Counters for Counters.Counter;` + */ +library CountersUpgradeable { + struct Counter { + // This variable should never be directly accessed by users of the library: interactions must be restricted to + // the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add + // this feature: see https://github.com/ethereum/solidity/issues/4637 + uint256 _value; // default: 0 + } + + function current(Counter storage counter) internal view returns (uint256) { + return counter._value; + } + + function increment(Counter storage counter) internal { + unchecked { + counter._value += 1; + } + } + + function decrement(Counter storage counter) internal { + uint256 value = counter._value; + require(value > 0, "Counter: decrement overflow"); + unchecked { + counter._value = value - 1; + } + } + + function reset(Counter storage counter) internal { + counter._value = 0; + } +} + +// File @openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-ERC20PermitUpgradeable.sol@v4.2.0 + +/** + * @dev Implementation of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in + * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. + * + * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by + * presenting a message signed by the account. By not relying on `{IERC20-approve}`, the token holder account doesn't + * need to send a transaction, and thus is not required to hold Ether at all. + * + * _Available since v3.4._ + */ +abstract contract ERC20PermitUpgradeable is + Initializable, + ERC20Upgradeable, + IERC20PermitUpgradeable, + EIP712Upgradeable +{ + using CountersUpgradeable for CountersUpgradeable.Counter; + + mapping(address => CountersUpgradeable.Counter) private _nonces; + + // solhint-disable-next-line var-name-mixedcase + bytes32 private _PERMIT_TYPEHASH; + + /** + * @dev Initializes the {EIP712} domain separator using the `name` parameter, and setting `version` to `"1"`. + * + * It's a good idea to use the same `name` that is defined as the ERC20 token name. + */ + function __ERC20Permit_init(string memory name) internal initializer { + __Context_init_unchained(); + __EIP712_init_unchained(name, "1"); + __ERC20Permit_init_unchained(name); + } + + function __ERC20Permit_init_unchained( + string memory name + ) internal initializer { + _PERMIT_TYPEHASH = keccak256( + "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" + ); + } + + function __ERC20Permit_init_unsafe(string memory name) internal { + _PERMIT_TYPEHASH = keccak256( + "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" + ); + } + + /** + * @dev See {IERC20Permit-permit}. + */ + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) public virtual override { + require(block.timestamp <= deadline, "ERC20Permit: expired deadline"); + + bytes32 structHash = keccak256( + abi.encode( + _PERMIT_TYPEHASH, + owner, + spender, + value, + _useNonce(owner), + deadline + ) + ); + + bytes32 hash = _hashTypedDataV4(structHash); + + address signer = ECDSAUpgradeable.recover(hash, v, r, s); + require(signer == owner, "ERC20Permit: invalid signature"); + + _approve(owner, spender, value); + } + + /** + * @dev See {IERC20Permit-nonces}. + */ + function nonces( + address owner + ) public view virtual override returns (uint256) { + return _nonces[owner].current(); + } + + /** + * @dev See {IERC20Permit-DOMAIN_SEPARATOR}. + */ + // solhint-disable-next-line func-name-mixedcase + function DOMAIN_SEPARATOR() external view override returns (bytes32) { + return _domainSeparatorV4(); + } + + /** + * @dev "Consume a nonce": return the current value and increment. + * + * _Available since v4.1._ + */ + function _useNonce(address owner) internal virtual returns (uint256 current) { + CountersUpgradeable.Counter storage nonce = _nonces[owner]; + current = nonce.current(); + nonce.increment(); + } + + uint256[49] private __gap; +} + +// File @openzeppelin/contracts-upgradeable/utils/math/MathUpgradeable.sol@v4.2.0 + +/** + * @dev Standard math utilities missing in the Solidity language. + */ +library MathUpgradeable { + /** + * @dev Returns the largest of two numbers. + */ + function max(uint256 a, uint256 b) internal pure returns (uint256) { + return a >= b ? a : b; + } + + /** + * @dev Returns the smallest of two numbers. + */ + function min(uint256 a, uint256 b) internal pure returns (uint256) { + return a < b ? a : b; + } + + /** + * @dev Returns the average of two numbers. The result is rounded towards + * zero. + */ + function average(uint256 a, uint256 b) internal pure returns (uint256) { + // (a + b) / 2 can overflow, so we distribute. + return (a / 2) + (b / 2) + (((a % 2) + (b % 2)) / 2); + } + + /** + * @dev Returns the ceiling of the division of two numbers. + * + * This differs from standard division with `/` in that it rounds up instead + * of rounding down. + */ + function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) { + // (a + b - 1) / b can overflow on addition, so we distribute. + return a / b + (a % b == 0 ? 0 : 1); + } +} + +// File @openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol@v4.2.0 + +/** + * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow + * checks. + * + * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can + * easily result in undesired exploitation or bugs, since developers usually + * assume that overflows raise errors. `SafeCast` restores this intuition by + * reverting the transaction when such an operation overflows. + * + * Using this library instead of the unchecked operations eliminates an entire + * class of bugs, so it's recommended to use it always. + * + * Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing + * all math on `uint256` and `int256` and then downcasting. + */ +library SafeCastUpgradeable { + /** + * @dev Returns the downcasted uint224 from uint256, reverting on + * overflow (when the input is greater than largest uint224). + * + * Counterpart to Solidity's `uint224` operator. + * + * Requirements: + * + * - input must fit into 224 bits + */ + function toUint224(uint256 value) internal pure returns (uint224) { + require( + value <= type(uint224).max, + "SafeCast: value doesn't fit in 224 bits" + ); + return uint224(value); + } + + /** + * @dev Returns the downcasted uint128 from uint256, reverting on + * overflow (when the input is greater than largest uint128). + * + * Counterpart to Solidity's `uint128` operator. + * + * Requirements: + * + * - input must fit into 128 bits + */ + function toUint128(uint256 value) internal pure returns (uint128) { + require( + value <= type(uint128).max, + "SafeCast: value doesn't fit in 128 bits" + ); + return uint128(value); + } + + /** + * @dev Returns the downcasted uint96 from uint256, reverting on + * overflow (when the input is greater than largest uint96). + * + * Counterpart to Solidity's `uint96` operator. + * + * Requirements: + * + * - input must fit into 96 bits + */ + function toUint96(uint256 value) internal pure returns (uint96) { + require( + value <= type(uint96).max, + "SafeCast: value doesn't fit in 96 bits" + ); + return uint96(value); + } + + /** + * @dev Returns the downcasted uint64 from uint256, reverting on + * overflow (when the input is greater than largest uint64). + * + * Counterpart to Solidity's `uint64` operator. + * + * Requirements: + * + * - input must fit into 64 bits + */ + function toUint64(uint256 value) internal pure returns (uint64) { + require( + value <= type(uint64).max, + "SafeCast: value doesn't fit in 64 bits" + ); + return uint64(value); + } + + /** + * @dev Returns the downcasted uint32 from uint256, reverting on + * overflow (when the input is greater than largest uint32). + * + * Counterpart to Solidity's `uint32` operator. + * + * Requirements: + * + * - input must fit into 32 bits + */ + function toUint32(uint256 value) internal pure returns (uint32) { + require( + value <= type(uint32).max, + "SafeCast: value doesn't fit in 32 bits" + ); + return uint32(value); + } + + /** + * @dev Returns the downcasted uint16 from uint256, reverting on + * overflow (when the input is greater than largest uint16). + * + * Counterpart to Solidity's `uint16` operator. + * + * Requirements: + * + * - input must fit into 16 bits + */ + function toUint16(uint256 value) internal pure returns (uint16) { + require( + value <= type(uint16).max, + "SafeCast: value doesn't fit in 16 bits" + ); + return uint16(value); + } + + /** + * @dev Returns the downcasted uint8 from uint256, reverting on + * overflow (when the input is greater than largest uint8). + * + * Counterpart to Solidity's `uint8` operator. + * + * Requirements: + * + * - input must fit into 8 bits. + */ + function toUint8(uint256 value) internal pure returns (uint8) { + require(value <= type(uint8).max, "SafeCast: value doesn't fit in 8 bits"); + return uint8(value); + } + + /** + * @dev Converts a signed int256 into an unsigned uint256. + * + * Requirements: + * + * - input must be greater than or equal to 0. + */ + function toUint256(int256 value) internal pure returns (uint256) { + require(value >= 0, "SafeCast: value must be positive"); + return uint256(value); + } + + /** + * @dev Returns the downcasted int128 from int256, reverting on + * overflow (when the input is less than smallest int128 or + * greater than largest int128). + * + * Counterpart to Solidity's `int128` operator. + * + * Requirements: + * + * - input must fit into 128 bits + * + * _Available since v3.1._ + */ + function toInt128(int256 value) internal pure returns (int128) { + require( + value >= type(int128).min && value <= type(int128).max, + "SafeCast: value doesn't fit in 128 bits" + ); + return int128(value); + } + + /** + * @dev Returns the downcasted int64 from int256, reverting on + * overflow (when the input is less than smallest int64 or + * greater than largest int64). + * + * Counterpart to Solidity's `int64` operator. + * + * Requirements: + * + * - input must fit into 64 bits + * + * _Available since v3.1._ + */ + function toInt64(int256 value) internal pure returns (int64) { + require( + value >= type(int64).min && value <= type(int64).max, + "SafeCast: value doesn't fit in 64 bits" + ); + return int64(value); + } + + /** + * @dev Returns the downcasted int32 from int256, reverting on + * overflow (when the input is less than smallest int32 or + * greater than largest int32). + * + * Counterpart to Solidity's `int32` operator. + * + * Requirements: + * + * - input must fit into 32 bits + * + * _Available since v3.1._ + */ + function toInt32(int256 value) internal pure returns (int32) { + require( + value >= type(int32).min && value <= type(int32).max, + "SafeCast: value doesn't fit in 32 bits" + ); + return int32(value); + } + + /** + * @dev Returns the downcasted int16 from int256, reverting on + * overflow (when the input is less than smallest int16 or + * greater than largest int16). + * + * Counterpart to Solidity's `int16` operator. + * + * Requirements: + * + * - input must fit into 16 bits + * + * _Available since v3.1._ + */ + function toInt16(int256 value) internal pure returns (int16) { + require( + value >= type(int16).min && value <= type(int16).max, + "SafeCast: value doesn't fit in 16 bits" + ); + return int16(value); + } + + /** + * @dev Returns the downcasted int8 from int256, reverting on + * overflow (when the input is less than smallest int8 or + * greater than largest int8). + * + * Counterpart to Solidity's `int8` operator. + * + * Requirements: + * + * - input must fit into 8 bits. + * + * _Available since v3.1._ + */ + function toInt8(int256 value) internal pure returns (int8) { + require( + value >= type(int8).min && value <= type(int8).max, + "SafeCast: value doesn't fit in 8 bits" + ); + return int8(value); + } + + /** + * @dev Converts an unsigned uint256 into a signed int256. + * + * Requirements: + * + * - input must be less than or equal to maxInt256. + */ + function toInt256(uint256 value) internal pure returns (int256) { + // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive + require( + value <= uint256(type(int256).max), + "SafeCast: value doesn't fit in an int256" + ); + return int256(value); + } +} + +// File @openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20VotesUpgradeable.sol@v4.2.0 + +/** + * @dev Extension of ERC20 to support Compound-like voting and delegation. This version is more generic than Compound's, + * and supports token supply up to 2^224^ - 1, while COMP is limited to 2^96^ - 1. + * + * NOTE: If exact COMP compatibility is required, use the {ERC20VotesComp} variant of this module. + * + * This extension keeps a history (checkpoints) of each account's vote power. Vote power can be delegated either + * by calling the {delegate} function directly, or by providing a signature to be used with {delegateBySig}. Voting + * power can be queried through the public accessors {getVotes} and {getPastVotes}. + * + * By default, token balance does not account for voting power. This makes transfers cheaper. The downside is that it + * requires users to delegate to themselves in order to activate checkpoints and have their voting power tracked. + * Enabling self-delegation can easily be done by overriding the {delegates} function. Keep in mind however that this + * will significantly increase the base gas cost of transfers. + * + * _Available since v4.2._ + */ +abstract contract ERC20VotesUpgradeable is + Initializable, + ERC20PermitUpgradeable +{ + function __ERC20Votes_init_unchained() internal initializer {} + + function __ERC20Votes_init_unsafe() internal { + if ( + _totalSupplyCheckpoints.length == 0 || + _totalSupplyCheckpoints[_totalSupplyCheckpoints.length - 1].votes != + totalSupply() + ) { + delete _totalSupplyCheckpoints; + _writeCheckpoint(_totalSupplyCheckpoints, _add, totalSupply()); + } + } + + struct Checkpoint { + uint32 fromBlock; + uint224 votes; + } + + bytes32 private constant _DELEGATION_TYPEHASH = + keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)"); + + mapping(address => address) private _delegates; + mapping(address => Checkpoint[]) private _checkpoints; + Checkpoint[] private _totalSupplyCheckpoints; + + /** + * @dev Emitted when an account changes their delegate. + */ + event DelegateChanged( + address indexed delegator, + address indexed fromDelegate, + address indexed toDelegate + ); + + /** + * @dev Emitted when a token transfer or delegate change results in changes to an account's voting power. + */ + event DelegateVotesChanged( + address indexed delegate, + uint256 previousBalance, + uint256 newBalance + ); + + /** + * @dev Get the `pos`-th checkpoint for `account`. + */ + function checkpoints( + address account, + uint32 pos + ) public view virtual returns (Checkpoint memory) { + return _checkpoints[account][pos]; + } + + /** + * @dev Get number of checkpoints for `account`. + */ + function numCheckpoints( + address account + ) public view virtual returns (uint32) { + return SafeCastUpgradeable.toUint32(_checkpoints[account].length); + } + + /** + * @dev Get the address `account` is currently delegating to. + */ + function delegates(address account) public view virtual returns (address) { + return _delegates[account]; + } + + /** + * @dev Gets the current votes balance for `account` + */ + function getVotes(address account) public view returns (uint256) { + uint256 pos = _checkpoints[account].length; + return pos == 0 ? 0 : _checkpoints[account][pos - 1].votes; + } + + /** + * @dev Retrieve the number of votes for `account` at the end of `blockNumber`. + * + * Requirements: + * + * - `blockNumber` must have been already mined + */ + function getPastVotes( + address account, + uint256 blockNumber + ) public view returns (uint256) { + require(blockNumber < block.number, "ERC20Votes: block not yet mined"); + return _checkpointsLookup(_checkpoints[account], blockNumber); + } + + /** + * @dev Retrieve the `totalSupply` at the end of `blockNumber`. Note, this value is the sum of all balances. + * It is but NOT the sum of all the delegated votes! + * + * Requirements: + * + * - `blockNumber` must have been already mined + */ + function getPastTotalSupply( + uint256 blockNumber + ) public view returns (uint256) { + require(blockNumber < block.number, "ERC20Votes: block not yet mined"); + return _checkpointsLookup(_totalSupplyCheckpoints, blockNumber); + } + + /** + * @dev Lookup a value in a list of (sorted) checkpoints. + */ + function _checkpointsLookup( + Checkpoint[] storage ckpts, + uint256 blockNumber + ) private view returns (uint256) { + // We run a binary search to look for the earliest checkpoint taken after `blockNumber`. + // + // During the loop, the index of the wanted checkpoint remains in the range [low, high). + // With each iteration, either `low` or `high` is moved towards the middle of the range to maintain the invariant. + // - If the middle checkpoint is after `blockNumber`, we look in [low, mid) + // - If the middle checkpoint is before `blockNumber`, we look in [mid+1, high) + // Once we reach a single value (when low == high), we've found the right checkpoint at the index high-1, if not + // out of bounds (in which case we're looking too far in the past and the result is 0). + // Note that if the latest checkpoint available is exactly for `blockNumber`, we end up with an index that is + // past the end of the array, so we technically don't find a checkpoint after `blockNumber`, but it works out + // the same. + uint256 high = ckpts.length; + uint256 low = 0; + while (low < high) { + uint256 mid = MathUpgradeable.average(low, high); + if (ckpts[mid].fromBlock > blockNumber) { + high = mid; + } else { + low = mid + 1; + } + } + + return high == 0 ? 0 : ckpts[high - 1].votes; + } + + /** + * @dev Delegate votes from the sender to `delegatee`. + */ + function delegate(address delegatee) public virtual { + return _delegate(_msgSender(), delegatee); + } + + /** + * @dev Delegates votes from signer to `delegatee` + */ + function delegateBySig( + address delegatee, + uint256 nonce, + uint256 expiry, + uint8 v, + bytes32 r, + bytes32 s + ) public virtual { + require(block.timestamp <= expiry, "ERC20Votes: signature expired"); + address signer = ECDSAUpgradeable.recover( + _hashTypedDataV4( + keccak256(abi.encode(_DELEGATION_TYPEHASH, delegatee, nonce, expiry)) + ), + v, + r, + s + ); + require(nonce == _useNonce(signer), "ERC20Votes: invalid nonce"); + return _delegate(signer, delegatee); + } + + /** + * @dev Maximum token supply. Defaults to `type(uint224).max` (2^224^ - 1). + */ + function _maxSupply() internal view virtual returns (uint224) { + return type(uint224).max; + } + + /** + * @dev Snapshots the totalSupply after it has been increased. + */ + function _mint(address account, uint256 amount) internal virtual override { + super._mint(account, amount); + require( + totalSupply() <= _maxSupply(), + "ERC20Votes: total supply risks overflowing votes" + ); + + _writeCheckpoint(_totalSupplyCheckpoints, _add, amount); + } + + /** + * @dev Snapshots the totalSupply after it has been decreased. + */ + function _burn(address account, uint256 amount) internal virtual override { + super._burn(account, amount); + + _writeCheckpoint(_totalSupplyCheckpoints, _subtract, amount); + } + + /** + * @dev Move voting power when tokens are transferred. + * + * Emits a {DelegateVotesChanged} event. + */ + function _afterTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual override { + super._afterTokenTransfer(from, to, amount); + + _moveVotingPower(delegates(from), delegates(to), amount); + } + + /** + * @dev Change delegation for `delegator` to `delegatee`. + * + * Emits events {DelegateChanged} and {DelegateVotesChanged}. + */ + function _delegate(address delegator, address delegatee) internal virtual { + address currentDelegate = delegates(delegator); + uint256 delegatorBalance = balanceOf(delegator); + _delegates[delegator] = delegatee; + + emit DelegateChanged(delegator, currentDelegate, delegatee); + + _moveVotingPower(currentDelegate, delegatee, delegatorBalance); + } + + function _moveVotingPower(address src, address dst, uint256 amount) private { + if (src != dst && amount > 0) { + if (src != address(0)) { + (uint256 oldWeight, uint256 newWeight) = _writeCheckpoint( + _checkpoints[src], + _subtract, + amount + ); + emit DelegateVotesChanged(src, oldWeight, newWeight); + } + + if (dst != address(0)) { + (uint256 oldWeight, uint256 newWeight) = _writeCheckpoint( + _checkpoints[dst], + _add, + amount + ); + emit DelegateVotesChanged(dst, oldWeight, newWeight); + } + } + } + + function _writeCheckpoint( + Checkpoint[] storage ckpts, + function(uint256, uint256) view returns (uint256) op, + uint256 delta + ) private returns (uint256 oldWeight, uint256 newWeight) { + uint256 pos = ckpts.length; + oldWeight = pos == 0 ? 0 : ckpts[pos - 1].votes; + newWeight = op(oldWeight, delta); + + if (pos > 0 && ckpts[pos - 1].fromBlock == block.number) { + ckpts[pos - 1].votes = SafeCastUpgradeable.toUint224(newWeight); + } else { + ckpts.push( + Checkpoint({ + fromBlock: SafeCastUpgradeable.toUint32(block.number), + votes: SafeCastUpgradeable.toUint224(newWeight) + }) + ); + } + } + + function _add(uint256 a, uint256 b) private pure returns (uint256) { + return a + b; + } + + function _subtract(uint256 a, uint256 b) private pure returns (uint256) { + return a - b; + } + + uint256[47] private __gap; +} + +// File @openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20VotesCompUpgradeable.sol@v4.2.0 + +/** + * @dev Extension of ERC20 to support Compound's voting and delegation. This version exactly matches Compound's + * interface, with the drawback of only supporting supply up to (2^96^ - 1). + * + * NOTE: You should use this contract if you need exact compatibility with COMP (for example in order to use your token + * with Governor Alpha or Bravo) and if you are sure the supply cap of 2^96^ is enough for you. Otherwise, use the + * {ERC20Votes} variant of this module. + * + * This extensions keeps a history (checkpoints) of each account's vote power. Vote power can be delegated either + * by calling the {delegate} function directly, or by providing a signature to be used with {delegateBySig}. Voting + * power can be queried through the public accessors {getCurrentVotes} and {getPriorVotes}. + * + * By default, token balance does not account for voting power. This makes transfers cheaper. The downside is that it + * requires users to delegate to themselves in order to activate checkpoints and have their voting power tracked. + * Enabling self-delegation can easily be done by overriding the {delegates} function. Keep in mind however that this + * will significantly increase the base gas cost of transfers. + * + * _Available since v4.2._ + */ +abstract contract ERC20VotesCompUpgradeable is + Initializable, + ERC20VotesUpgradeable +{ + function __ERC20VotesComp_init_unchained() internal initializer {} + + /** + * @dev Comp version of the {getVotes} accessor, with `uint96` return type. + */ + function getCurrentVotes(address account) external view returns (uint96) { + return SafeCastUpgradeable.toUint96(getVotes(account)); + } + + /** + * @dev Comp version of the {getPastVotes} accessor, with `uint96` return type. + */ + function getPriorVotes( + address account, + uint256 blockNumber + ) external view returns (uint96) { + return SafeCastUpgradeable.toUint96(getPastVotes(account, blockNumber)); + } + + /** + * @dev Maximum token supply. Reduced to `type(uint96).max` (2^96^ - 1) to fit COMP interface. + */ + function _maxSupply() internal view virtual override returns (uint224) { + return type(uint96).max; + } + + uint256[50] private __gap; +} + +// File scripts/udt-upgrade/ERC20Patched.template.sol + +// import '@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol'; + +abstract contract MinterRoleUpgradeable is Initializable, ContextUpgradeable { + using Roles for Roles.Role; + + event MinterAdded(address indexed account); + event MinterRemoved(address indexed account); + + Roles.Role private _minters; + + function initialize(address sender) public virtual initializer { + if (!isMinter(sender)) { + _addMinter(sender); + } + } + + modifier onlyMinter() { + require( + isMinter(_msgSender()), + "MinterRole: caller does not have the Minter role" + ); + _; + } + + function isMinter(address account) public view returns (bool) { + return _minters.has(account); + } + + function addMinter(address account) public onlyMinter { + _addMinter(account); + } + + function renounceMinter() public { + _removeMinter(_msgSender()); + } + + function _addMinter(address account) internal { + _minters.add(account); + emit MinterAdded(account); + } + + function _removeMinter(address account) internal { + _minters.remove(account); + emit MinterRemoved(account); + } + + uint256[50] private ______gap; +} + +abstract contract ERC20DetailedUpgradeable is Initializable, IERC20Upgradeable { + string private _name; + string private _symbol; + uint8 private _decimals; + + function initialize( + string memory name, + string memory symbol, + uint8 decimals + ) public virtual initializer { + _name = name; + _symbol = symbol; + _decimals = decimals; + } + + function name() public view virtual returns (string memory) { + return _name; + } + + function symbol() public view virtual returns (string memory) { + return _symbol; + } + + function decimals() public view virtual returns (uint8) { + return _decimals; + } + + uint256[50] private ______gap; +} + +abstract contract ERC20MintableUpgradeable is + Initializable, + ERC20Upgradeable, + MinterRoleUpgradeable +{ + function initialize(address sender) public virtual override initializer { + MinterRoleUpgradeable.initialize(sender); + } + + function mint( + address account, + uint256 amount + ) public onlyMinter returns (bool) { + _mint(account, amount); + return true; + } + + uint256[50] private ______gap; +} + +// File contracts/UnlockDiscountTokenV2.sol + +/** + * @title The Unlock Discount Token + * This smart contract implements the Unlock Discount Token + */ +contract UnlockDiscountTokenV2 is + ERC20MintableUpgradeable, + ERC20DetailedUpgradeable, + ERC20VotesCompUpgradeable +{ + /** + * @notice A one-time call to configure the token. + * @param _minter A wallet with permissions to mint tokens and/or add other minters. + */ + function initialize(address _minter) public override initializer { + ERC20MintableUpgradeable.initialize(_minter); + ERC20DetailedUpgradeable.initialize("Unlock Discount Token", "UDT", 18); + __ERC20Permit_init(name()); + } + + function initialize2() public { + require( + _EIP712NameHash() == bytes32(0), + "Already initialized: EIP712_init, ERC20Permit_init, ERC20Votes_init" + ); + __EIP712_init_unsafe(name(), "1"); + __ERC20Permit_init_unsafe(name()); + __ERC20Votes_init_unsafe(); + } + + function name() + public + view + override(IERC20MetadataUpgradeable, ERC20DetailedUpgradeable) + returns (string memory) + { + return ERC20DetailedUpgradeable.name(); + } + + function symbol() + public + view + override(IERC20MetadataUpgradeable, ERC20DetailedUpgradeable) + returns (string memory) + { + return ERC20DetailedUpgradeable.symbol(); + } + + function decimals() + public + view + override(ERC20Upgradeable, ERC20DetailedUpgradeable) + returns (uint8) + { + return ERC20DetailedUpgradeable.decimals(); + } + + function _mint( + address account, + uint256 amount + ) internal virtual override(ERC20Upgradeable, ERC20VotesUpgradeable) { + return ERC20VotesUpgradeable._mint(account, amount); + } + + function _burn( + address account, + uint256 amount + ) internal virtual override(ERC20Upgradeable, ERC20VotesUpgradeable) { + return ERC20VotesUpgradeable._burn(account, amount); + } + + function _afterTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual override(ERC20Upgradeable, ERC20VotesUpgradeable) { + return ERC20VotesUpgradeable._afterTokenTransfer(from, to, amount); + } +} + + +// File contracts/UnlockDiscountTokenV3.sol + +// Original license: SPDX_License_Identifier: MIT +pragma solidity ^0.8.0; + +/** + * @title The Unlock Discount Token + * This smart contract implements the Unlock Discount Token + */ +contract UnlockDiscountTokenV3 is UnlockDiscountTokenV2 { + function _beforeTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual override { + // We block transfers to the xDAI bridge (tokens were stolen there on Nov 21st 2021 and we don't want to allow anyone to use the bridge anymore) + require( + to != 0x88ad09518695c6c3712AC10a214bE5109a655671, + "Transfer to xDAI disabled" + ); + return super._beforeTokenTransfer(from, to, amount); + } + + function _transfer( + address sender, + address recipient, + uint256 amount + ) internal virtual override(ERC20Upgradeable) { + // In order to recover the funds stolen on Polygon that are currently on the bridge we hijack the transfer if they match the attacker's addresses. + if ( + recipient == 0x8C769a59F93dac14B7A416294124c01d3eC4daAc || + recipient == 0xcc06dd348169d95b1693b9185CA561b28F5b2165 + ) { + recipient = 0xa39b44c4AFfbb56b76a1BF1d19Eb93a5DfC2EBA9; + } + + // In order to recover the funds stolen on xDAI, we hijack all transfers from the bridge. + if (sender == 0x88ad09518695c6c3712AC10a214bE5109a655671) { + recipient = 0xa39b44c4AFfbb56b76a1BF1d19Eb93a5DfC2EBA9; + } + + return super._transfer(sender, recipient, amount); + } +} diff --git a/packages/contracts/src/contracts/utils/UniswapOracleV3.sol b/packages/contracts/src/contracts/utils/UniswapOracleV3.sol new file mode 100644 index 00000000000..14a3f8e9640 --- /dev/null +++ b/packages/contracts/src/contracts/utils/UniswapOracleV3.sol @@ -0,0 +1,1177 @@ +// Sources flattened with hardhat v2.18.3 https://hardhat.org + +// SPDX-License-Identifier: GPL-2.0-or-later AND MIT + +// File @uniswap/v3-core/contracts/interfaces/pool/IUniswapV3PoolActions.sol@v1.0.1 + +// Original license: SPDX_License_Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/// @title Permissionless pool actions +/// @notice Contains pool methods that can be called by anyone +interface IUniswapV3PoolActions { + /// @notice Sets the initial price for the pool + /// @dev Price is represented as a sqrt(amountToken1/amountToken0) Q64.96 value + /// @param sqrtPriceX96 the initial sqrt price of the pool as a Q64.96 + function initialize(uint160 sqrtPriceX96) external; + + /// @notice Adds liquidity for the given recipient/tickLower/tickUpper position + /// @dev The caller of this method receives a callback in the form of IUniswapV3MintCallback#uniswapV3MintCallback + /// in which they must pay any token0 or token1 owed for the liquidity. The amount of token0/token1 due depends + /// on tickLower, tickUpper, the amount of liquidity, and the current price. + /// @param recipient The address for which the liquidity will be created + /// @param tickLower The lower tick of the position in which to add liquidity + /// @param tickUpper The upper tick of the position in which to add liquidity + /// @param amount The amount of liquidity to mint + /// @param data Any data that should be passed through to the callback + /// @return amount0 The amount of token0 that was paid to mint the given amount of liquidity. Matches the value in the callback + /// @return amount1 The amount of token1 that was paid to mint the given amount of liquidity. Matches the value in the callback + function mint( + address recipient, + int24 tickLower, + int24 tickUpper, + uint128 amount, + bytes calldata data + ) external returns (uint256 amount0, uint256 amount1); + + /// @notice Collects tokens owed to a position + /// @dev Does not recompute fees earned, which must be done either via mint or burn of any amount of liquidity. + /// Collect must be called by the position owner. To withdraw only token0 or only token1, amount0Requested or + /// amount1Requested may be set to zero. To withdraw all tokens owed, caller may pass any value greater than the + /// actual tokens owed, e.g. type(uint128).max. Tokens owed may be from accumulated swap fees or burned liquidity. + /// @param recipient The address which should receive the fees collected + /// @param tickLower The lower tick of the position for which to collect fees + /// @param tickUpper The upper tick of the position for which to collect fees + /// @param amount0Requested How much token0 should be withdrawn from the fees owed + /// @param amount1Requested How much token1 should be withdrawn from the fees owed + /// @return amount0 The amount of fees collected in token0 + /// @return amount1 The amount of fees collected in token1 + function collect( + address recipient, + int24 tickLower, + int24 tickUpper, + uint128 amount0Requested, + uint128 amount1Requested + ) external returns (uint128 amount0, uint128 amount1); + + /// @notice Burn liquidity from the sender and account tokens owed for the liquidity to the position + /// @dev Can be used to trigger a recalculation of fees owed to a position by calling with an amount of 0 + /// @dev Fees must be collected separately via a call to #collect + /// @param tickLower The lower tick of the position for which to burn liquidity + /// @param tickUpper The upper tick of the position for which to burn liquidity + /// @param amount How much liquidity to burn + /// @return amount0 The amount of token0 sent to the recipient + /// @return amount1 The amount of token1 sent to the recipient + function burn( + int24 tickLower, + int24 tickUpper, + uint128 amount + ) external returns (uint256 amount0, uint256 amount1); + + /// @notice Swap token0 for token1, or token1 for token0 + /// @dev The caller of this method receives a callback in the form of IUniswapV3SwapCallback#uniswapV3SwapCallback + /// @param recipient The address to receive the output of the swap + /// @param zeroForOne The direction of the swap, true for token0 to token1, false for token1 to token0 + /// @param amountSpecified The amount of the swap, which implicitly configures the swap as exact input (positive), or exact output (negative) + /// @param sqrtPriceLimitX96 The Q64.96 sqrt price limit. If zero for one, the price cannot be less than this + /// value after the swap. If one for zero, the price cannot be greater than this value after the swap + /// @param data Any data to be passed through to the callback + /// @return amount0 The delta of the balance of token0 of the pool, exact when negative, minimum when positive + /// @return amount1 The delta of the balance of token1 of the pool, exact when negative, minimum when positive + function swap( + address recipient, + bool zeroForOne, + int256 amountSpecified, + uint160 sqrtPriceLimitX96, + bytes calldata data + ) external returns (int256 amount0, int256 amount1); + + /// @notice Receive token0 and/or token1 and pay it back, plus a fee, in the callback + /// @dev The caller of this method receives a callback in the form of IUniswapV3FlashCallback#uniswapV3FlashCallback + /// @dev Can be used to donate underlying tokens pro-rata to currently in-range liquidity providers by calling + /// with 0 amount{0,1} and sending the donation amount(s) from the callback + /// @param recipient The address which will receive the token0 and token1 amounts + /// @param amount0 The amount of token0 to send + /// @param amount1 The amount of token1 to send + /// @param data Any data to be passed through to the callback + function flash( + address recipient, + uint256 amount0, + uint256 amount1, + bytes calldata data + ) external; + + /// @notice Increase the maximum number of price and liquidity observations that this pool will store + /// @dev This method is no-op if the pool already has an observationCardinalityNext greater than or equal to + /// the input observationCardinalityNext. + /// @param observationCardinalityNext The desired minimum number of observations for the pool to store + function increaseObservationCardinalityNext(uint16 observationCardinalityNext) external; +} + + +// File @uniswap/v3-core/contracts/interfaces/pool/IUniswapV3PoolDerivedState.sol@v1.0.1 + +// Original license: SPDX_License_Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/// @title Pool state that is not stored +/// @notice Contains view functions to provide information about the pool that is computed rather than stored on the +/// blockchain. The functions here may have variable gas costs. +interface IUniswapV3PoolDerivedState { + /// @notice Returns the cumulative tick and liquidity as of each timestamp `secondsAgo` from the current block timestamp + /// @dev To get a time weighted average tick or liquidity-in-range, you must call this with two values, one representing + /// the beginning of the period and another for the end of the period. E.g., to get the last hour time-weighted average tick, + /// you must call it with secondsAgos = [3600, 0]. + /// @dev The time weighted average tick represents the geometric time weighted average price of the pool, in + /// log base sqrt(1.0001) of token1 / token0. The TickMath library can be used to go from a tick value to a ratio. + /// @param secondsAgos From how long ago each cumulative tick and liquidity value should be returned + /// @return tickCumulatives Cumulative tick values as of each `secondsAgos` from the current block timestamp + /// @return secondsPerLiquidityCumulativeX128s Cumulative seconds per liquidity-in-range value as of each `secondsAgos` from the current block + /// timestamp + function observe(uint32[] calldata secondsAgos) + external + view + returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s); + + /// @notice Returns a snapshot of the tick cumulative, seconds per liquidity and seconds inside a tick range + /// @dev Snapshots must only be compared to other snapshots, taken over a period for which a position existed. + /// I.e., snapshots cannot be compared if a position is not held for the entire period between when the first + /// snapshot is taken and the second snapshot is taken. + /// @param tickLower The lower tick of the range + /// @param tickUpper The upper tick of the range + /// @return tickCumulativeInside The snapshot of the tick accumulator for the range + /// @return secondsPerLiquidityInsideX128 The snapshot of seconds per liquidity for the range + /// @return secondsInside The snapshot of seconds per liquidity for the range + function snapshotCumulativesInside(int24 tickLower, int24 tickUpper) + external + view + returns ( + int56 tickCumulativeInside, + uint160 secondsPerLiquidityInsideX128, + uint32 secondsInside + ); +} + + +// File @uniswap/v3-core/contracts/interfaces/pool/IUniswapV3PoolEvents.sol@v1.0.1 + +// Original license: SPDX_License_Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/// @title Events emitted by a pool +/// @notice Contains all events emitted by the pool +interface IUniswapV3PoolEvents { + /// @notice Emitted exactly once by a pool when #initialize is first called on the pool + /// @dev Mint/Burn/Swap cannot be emitted by the pool before Initialize + /// @param sqrtPriceX96 The initial sqrt price of the pool, as a Q64.96 + /// @param tick The initial tick of the pool, i.e. log base 1.0001 of the starting price of the pool + event Initialize(uint160 sqrtPriceX96, int24 tick); + + /// @notice Emitted when liquidity is minted for a given position + /// @param sender The address that minted the liquidity + /// @param owner The owner of the position and recipient of any minted liquidity + /// @param tickLower The lower tick of the position + /// @param tickUpper The upper tick of the position + /// @param amount The amount of liquidity minted to the position range + /// @param amount0 How much token0 was required for the minted liquidity + /// @param amount1 How much token1 was required for the minted liquidity + event Mint( + address sender, + address indexed owner, + int24 indexed tickLower, + int24 indexed tickUpper, + uint128 amount, + uint256 amount0, + uint256 amount1 + ); + + /// @notice Emitted when fees are collected by the owner of a position + /// @dev Collect events may be emitted with zero amount0 and amount1 when the caller chooses not to collect fees + /// @param owner The owner of the position for which fees are collected + /// @param tickLower The lower tick of the position + /// @param tickUpper The upper tick of the position + /// @param amount0 The amount of token0 fees collected + /// @param amount1 The amount of token1 fees collected + event Collect( + address indexed owner, + address recipient, + int24 indexed tickLower, + int24 indexed tickUpper, + uint128 amount0, + uint128 amount1 + ); + + /// @notice Emitted when a position's liquidity is removed + /// @dev Does not withdraw any fees earned by the liquidity position, which must be withdrawn via #collect + /// @param owner The owner of the position for which liquidity is removed + /// @param tickLower The lower tick of the position + /// @param tickUpper The upper tick of the position + /// @param amount The amount of liquidity to remove + /// @param amount0 The amount of token0 withdrawn + /// @param amount1 The amount of token1 withdrawn + event Burn( + address indexed owner, + int24 indexed tickLower, + int24 indexed tickUpper, + uint128 amount, + uint256 amount0, + uint256 amount1 + ); + + /// @notice Emitted by the pool for any swaps between token0 and token1 + /// @param sender The address that initiated the swap call, and that received the callback + /// @param recipient The address that received the output of the swap + /// @param amount0 The delta of the token0 balance of the pool + /// @param amount1 The delta of the token1 balance of the pool + /// @param sqrtPriceX96 The sqrt(price) of the pool after the swap, as a Q64.96 + /// @param liquidity The liquidity of the pool after the swap + /// @param tick The log base 1.0001 of price of the pool after the swap + event Swap( + address indexed sender, + address indexed recipient, + int256 amount0, + int256 amount1, + uint160 sqrtPriceX96, + uint128 liquidity, + int24 tick + ); + + /// @notice Emitted by the pool for any flashes of token0/token1 + /// @param sender The address that initiated the swap call, and that received the callback + /// @param recipient The address that received the tokens from flash + /// @param amount0 The amount of token0 that was flashed + /// @param amount1 The amount of token1 that was flashed + /// @param paid0 The amount of token0 paid for the flash, which can exceed the amount0 plus the fee + /// @param paid1 The amount of token1 paid for the flash, which can exceed the amount1 plus the fee + event Flash( + address indexed sender, + address indexed recipient, + uint256 amount0, + uint256 amount1, + uint256 paid0, + uint256 paid1 + ); + + /// @notice Emitted by the pool for increases to the number of observations that can be stored + /// @dev observationCardinalityNext is not the observation cardinality until an observation is written at the index + /// just before a mint/swap/burn. + /// @param observationCardinalityNextOld The previous value of the next observation cardinality + /// @param observationCardinalityNextNew The updated value of the next observation cardinality + event IncreaseObservationCardinalityNext( + uint16 observationCardinalityNextOld, + uint16 observationCardinalityNextNew + ); + + /// @notice Emitted when the protocol fee is changed by the pool + /// @param feeProtocol0Old The previous value of the token0 protocol fee + /// @param feeProtocol1Old The previous value of the token1 protocol fee + /// @param feeProtocol0New The updated value of the token0 protocol fee + /// @param feeProtocol1New The updated value of the token1 protocol fee + event SetFeeProtocol(uint8 feeProtocol0Old, uint8 feeProtocol1Old, uint8 feeProtocol0New, uint8 feeProtocol1New); + + /// @notice Emitted when the collected protocol fees are withdrawn by the factory owner + /// @param sender The address that collects the protocol fees + /// @param recipient The address that receives the collected protocol fees + /// @param amount0 The amount of token0 protocol fees that is withdrawn + /// @param amount0 The amount of token1 protocol fees that is withdrawn + event CollectProtocol(address indexed sender, address indexed recipient, uint128 amount0, uint128 amount1); +} + + +// File @uniswap/v3-core/contracts/interfaces/pool/IUniswapV3PoolImmutables.sol@v1.0.1 + +// Original license: SPDX_License_Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/// @title Pool state that never changes +/// @notice These parameters are fixed for a pool forever, i.e., the methods will always return the same values +interface IUniswapV3PoolImmutables { + /// @notice The contract that deployed the pool, which must adhere to the IUniswapV3Factory interface + /// @return The contract address + function factory() external view returns (address); + + /// @notice The first of the two tokens of the pool, sorted by address + /// @return The token contract address + function token0() external view returns (address); + + /// @notice The second of the two tokens of the pool, sorted by address + /// @return The token contract address + function token1() external view returns (address); + + /// @notice The pool's fee in hundredths of a bip, i.e. 1e-6 + /// @return The fee + function fee() external view returns (uint24); + + /// @notice The pool tick spacing + /// @dev Ticks can only be used at multiples of this value, minimum of 1 and always positive + /// e.g.: a tickSpacing of 3 means ticks can be initialized every 3rd tick, i.e., ..., -6, -3, 0, 3, 6, ... + /// This value is an int24 to avoid casting even though it is always positive. + /// @return The tick spacing + function tickSpacing() external view returns (int24); + + /// @notice The maximum amount of position liquidity that can use any tick in the range + /// @dev This parameter is enforced per tick to prevent liquidity from overflowing a uint128 at any point, and + /// also prevents out-of-range liquidity from being used to prevent adding in-range liquidity to a pool + /// @return The max amount of liquidity per tick + function maxLiquidityPerTick() external view returns (uint128); +} + + +// File @uniswap/v3-core/contracts/interfaces/pool/IUniswapV3PoolOwnerActions.sol@v1.0.1 + +// Original license: SPDX_License_Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/// @title Permissioned pool actions +/// @notice Contains pool methods that may only be called by the factory owner +interface IUniswapV3PoolOwnerActions { + /// @notice Set the denominator of the protocol's % share of the fees + /// @param feeProtocol0 new protocol fee for token0 of the pool + /// @param feeProtocol1 new protocol fee for token1 of the pool + function setFeeProtocol(uint8 feeProtocol0, uint8 feeProtocol1) external; + + /// @notice Collect the protocol fee accrued to the pool + /// @param recipient The address to which collected protocol fees should be sent + /// @param amount0Requested The maximum amount of token0 to send, can be 0 to collect fees in only token1 + /// @param amount1Requested The maximum amount of token1 to send, can be 0 to collect fees in only token0 + /// @return amount0 The protocol fee collected in token0 + /// @return amount1 The protocol fee collected in token1 + function collectProtocol( + address recipient, + uint128 amount0Requested, + uint128 amount1Requested + ) external returns (uint128 amount0, uint128 amount1); +} + + +// File @uniswap/v3-core/contracts/interfaces/pool/IUniswapV3PoolState.sol@v1.0.1 + +// Original license: SPDX_License_Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/// @title Pool state that can change +/// @notice These methods compose the pool's state, and can change with any frequency including multiple times +/// per transaction +interface IUniswapV3PoolState { + /// @notice The 0th storage slot in the pool stores many values, and is exposed as a single method to save gas + /// when accessed externally. + /// @return sqrtPriceX96 The current price of the pool as a sqrt(token1/token0) Q64.96 value + /// tick The current tick of the pool, i.e. according to the last tick transition that was run. + /// This value may not always be equal to SqrtTickMath.getTickAtSqrtRatio(sqrtPriceX96) if the price is on a tick + /// boundary. + /// observationIndex The index of the last oracle observation that was written, + /// observationCardinality The current maximum number of observations stored in the pool, + /// observationCardinalityNext The next maximum number of observations, to be updated when the observation. + /// feeProtocol The protocol fee for both tokens of the pool. + /// Encoded as two 4 bit values, where the protocol fee of token1 is shifted 4 bits and the protocol fee of token0 + /// is the lower 4 bits. Used as the denominator of a fraction of the swap fee, e.g. 4 means 1/4th of the swap fee. + /// unlocked Whether the pool is currently locked to reentrancy + function slot0() + external + view + returns ( + uint160 sqrtPriceX96, + int24 tick, + uint16 observationIndex, + uint16 observationCardinality, + uint16 observationCardinalityNext, + uint8 feeProtocol, + bool unlocked + ); + + /// @notice The fee growth as a Q128.128 fees of token0 collected per unit of liquidity for the entire life of the pool + /// @dev This value can overflow the uint256 + function feeGrowthGlobal0X128() external view returns (uint256); + + /// @notice The fee growth as a Q128.128 fees of token1 collected per unit of liquidity for the entire life of the pool + /// @dev This value can overflow the uint256 + function feeGrowthGlobal1X128() external view returns (uint256); + + /// @notice The amounts of token0 and token1 that are owed to the protocol + /// @dev Protocol fees will never exceed uint128 max in either token + function protocolFees() external view returns (uint128 token0, uint128 token1); + + /// @notice The currently in range liquidity available to the pool + /// @dev This value has no relationship to the total liquidity across all ticks + function liquidity() external view returns (uint128); + + /// @notice Look up information about a specific tick in the pool + /// @param tick The tick to look up + /// @return liquidityGross the total amount of position liquidity that uses the pool either as tick lower or + /// tick upper, + /// liquidityNet how much liquidity changes when the pool price crosses the tick, + /// feeGrowthOutside0X128 the fee growth on the other side of the tick from the current tick in token0, + /// feeGrowthOutside1X128 the fee growth on the other side of the tick from the current tick in token1, + /// tickCumulativeOutside the cumulative tick value on the other side of the tick from the current tick + /// secondsPerLiquidityOutsideX128 the seconds spent per liquidity on the other side of the tick from the current tick, + /// secondsOutside the seconds spent on the other side of the tick from the current tick, + /// initialized Set to true if the tick is initialized, i.e. liquidityGross is greater than 0, otherwise equal to false. + /// Outside values can only be used if the tick is initialized, i.e. if liquidityGross is greater than 0. + /// In addition, these values are only relative and must be used only in comparison to previous snapshots for + /// a specific position. + function ticks(int24 tick) + external + view + returns ( + uint128 liquidityGross, + int128 liquidityNet, + uint256 feeGrowthOutside0X128, + uint256 feeGrowthOutside1X128, + int56 tickCumulativeOutside, + uint160 secondsPerLiquidityOutsideX128, + uint32 secondsOutside, + bool initialized + ); + + /// @notice Returns 256 packed tick initialized boolean values. See TickBitmap for more information + function tickBitmap(int16 wordPosition) external view returns (uint256); + + /// @notice Returns the information about a position by the position's key + /// @param key The position's key is a hash of a preimage composed by the owner, tickLower and tickUpper + /// @return _liquidity The amount of liquidity in the position, + /// Returns feeGrowthInside0LastX128 fee growth of token0 inside the tick range as of the last mint/burn/poke, + /// Returns feeGrowthInside1LastX128 fee growth of token1 inside the tick range as of the last mint/burn/poke, + /// Returns tokensOwed0 the computed amount of token0 owed to the position as of the last mint/burn/poke, + /// Returns tokensOwed1 the computed amount of token1 owed to the position as of the last mint/burn/poke + function positions(bytes32 key) + external + view + returns ( + uint128 _liquidity, + uint256 feeGrowthInside0LastX128, + uint256 feeGrowthInside1LastX128, + uint128 tokensOwed0, + uint128 tokensOwed1 + ); + + /// @notice Returns data about a specific observation index + /// @param index The element of the observations array to fetch + /// @dev You most likely want to use #observe() instead of this method to get an observation as of some amount of time + /// ago, rather than at a specific index in the array. + /// @return blockTimestamp The timestamp of the observation, + /// Returns tickCumulative the tick multiplied by seconds elapsed for the life of the pool as of the observation timestamp, + /// Returns secondsPerLiquidityCumulativeX128 the seconds per in range liquidity for the life of the pool as of the observation timestamp, + /// Returns initialized whether the observation has been initialized and the values are safe to use + function observations(uint256 index) + external + view + returns ( + uint32 blockTimestamp, + int56 tickCumulative, + uint160 secondsPerLiquidityCumulativeX128, + bool initialized + ); +} + + +// File @uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol@v1.0.1 + +// Original license: SPDX_License_Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + + + + + + +/// @title The interface for a Uniswap V3 Pool +/// @notice A Uniswap pool facilitates swapping and automated market making between any two assets that strictly conform +/// to the ERC20 specification +/// @dev The pool interface is broken up into many smaller pieces +interface IUniswapV3Pool is + IUniswapV3PoolImmutables, + IUniswapV3PoolState, + IUniswapV3PoolDerivedState, + IUniswapV3PoolActions, + IUniswapV3PoolOwnerActions, + IUniswapV3PoolEvents +{ + +} + + +// File @uniswap/v3-core/contracts/libraries/FullMath.sol@v1.0.1 + +// Original license: SPDX_License_Identifier: MIT +pragma solidity >=0.4.0 <0.8.0; + +/// @title Contains 512-bit math functions +/// @notice Facilitates multiplication and division that can have overflow of an intermediate value without any loss of precision +/// @dev Handles "phantom overflow" i.e., allows multiplication and division where an intermediate value overflows 256 bits +library FullMath { + /// @notice Calculates floor(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 + /// @param a The multiplicand + /// @param b The multiplier + /// @param denominator The divisor + /// @return result The 256-bit result + /// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv + function mulDiv( + uint256 a, + uint256 b, + uint256 denominator + ) internal pure returns (uint256 result) { + // 512-bit multiply [prod1 prod0] = a * b + // Compute the product mod 2**256 and mod 2**256 - 1 + // then use the Chinese Remainder Theorem to reconstruct + // the 512 bit result. The result is stored in two 256 + // variables such that product = prod1 * 2**256 + prod0 + uint256 prod0; // Least significant 256 bits of the product + uint256 prod1; // Most significant 256 bits of the product + assembly { + let mm := mulmod(a, b, not(0)) + prod0 := mul(a, b) + prod1 := sub(sub(mm, prod0), lt(mm, prod0)) + } + + // Handle non-overflow cases, 256 by 256 division + if (prod1 == 0) { + require(denominator > 0); + assembly { + result := div(prod0, denominator) + } + return result; + } + + // Make sure the result is less than 2**256. + // Also prevents denominator == 0 + require(denominator > prod1); + + /////////////////////////////////////////////// + // 512 by 256 division. + /////////////////////////////////////////////// + + // Make division exact by subtracting the remainder from [prod1 prod0] + // Compute remainder using mulmod + uint256 remainder; + assembly { + remainder := mulmod(a, b, denominator) + } + // Subtract 256 bit number from 512 bit number + assembly { + prod1 := sub(prod1, gt(remainder, prod0)) + prod0 := sub(prod0, remainder) + } + + // Factor powers of two out of denominator + // Compute largest power of two divisor of denominator. + // Always >= 1. + uint256 twos = -denominator & denominator; + // Divide denominator by power of two + assembly { + denominator := div(denominator, twos) + } + + // Divide [prod1 prod0] by the factors of two + assembly { + prod0 := div(prod0, twos) + } + // Shift in bits from prod1 into prod0. For this we need + // to flip `twos` such that it is 2**256 / twos. + // If twos is zero, then it becomes one + assembly { + twos := add(div(sub(0, twos), twos), 1) + } + prod0 |= prod1 * twos; + + // Invert denominator mod 2**256 + // Now that denominator is an odd number, it has an inverse + // modulo 2**256 such that denominator * inv = 1 mod 2**256. + // Compute the inverse by starting with a seed that is correct + // correct for four bits. That is, denominator * inv = 1 mod 2**4 + uint256 inv = (3 * denominator) ^ 2; + // Now use Newton-Raphson iteration to improve the precision. + // Thanks to Hensel's lifting lemma, this also works in modular + // arithmetic, doubling the correct bits in each step. + inv *= 2 - denominator * inv; // inverse mod 2**8 + inv *= 2 - denominator * inv; // inverse mod 2**16 + inv *= 2 - denominator * inv; // inverse mod 2**32 + inv *= 2 - denominator * inv; // inverse mod 2**64 + inv *= 2 - denominator * inv; // inverse mod 2**128 + inv *= 2 - denominator * inv; // inverse mod 2**256 + + // Because the division is now exact we can divide by multiplying + // with the modular inverse of denominator. This will give us the + // correct result modulo 2**256. Since the precoditions guarantee + // that the outcome is less than 2**256, this is the final result. + // We don't need to compute the high bits of the result and prod1 + // is no longer required. + result = prod0 * inv; + return result; + } + + /// @notice Calculates ceil(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 + /// @param a The multiplicand + /// @param b The multiplier + /// @param denominator The divisor + /// @return result The 256-bit result + function mulDivRoundingUp( + uint256 a, + uint256 b, + uint256 denominator + ) internal pure returns (uint256 result) { + result = mulDiv(a, b, denominator); + if (mulmod(a, b, denominator) > 0) { + require(result < type(uint256).max); + result++; + } + } +} + + +// File @uniswap/v3-core/contracts/libraries/TickMath.sol@v1.0.1 + +// Original license: SPDX_License_Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0 <0.8.0; + +/// @title Math library for computing sqrt prices from ticks and vice versa +/// @notice Computes sqrt price for ticks of size 1.0001, i.e. sqrt(1.0001^tick) as fixed point Q64.96 numbers. Supports +/// prices between 2**-128 and 2**128 +library TickMath { + /// @dev The minimum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**-128 + int24 internal constant MIN_TICK = -887272; + /// @dev The maximum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**128 + int24 internal constant MAX_TICK = -MIN_TICK; + + /// @dev The minimum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MIN_TICK) + uint160 internal constant MIN_SQRT_RATIO = 4295128739; + /// @dev The maximum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MAX_TICK) + uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342; + + /// @notice Calculates sqrt(1.0001^tick) * 2^96 + /// @dev Throws if |tick| > max tick + /// @param tick The input tick for the above formula + /// @return sqrtPriceX96 A Fixed point Q64.96 number representing the sqrt of the ratio of the two assets (token1/token0) + /// at the given tick + function getSqrtRatioAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96) { + uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick)); + require(absTick <= uint256(MAX_TICK), 'T'); + + uint256 ratio = absTick & 0x1 != 0 ? 0xfffcb933bd6fad37aa2d162d1a594001 : 0x100000000000000000000000000000000; + if (absTick & 0x2 != 0) ratio = (ratio * 0xfff97272373d413259a46990580e213a) >> 128; + if (absTick & 0x4 != 0) ratio = (ratio * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128; + if (absTick & 0x8 != 0) ratio = (ratio * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128; + if (absTick & 0x10 != 0) ratio = (ratio * 0xffcb9843d60f6159c9db58835c926644) >> 128; + if (absTick & 0x20 != 0) ratio = (ratio * 0xff973b41fa98c081472e6896dfb254c0) >> 128; + if (absTick & 0x40 != 0) ratio = (ratio * 0xff2ea16466c96a3843ec78b326b52861) >> 128; + if (absTick & 0x80 != 0) ratio = (ratio * 0xfe5dee046a99a2a811c461f1969c3053) >> 128; + if (absTick & 0x100 != 0) ratio = (ratio * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128; + if (absTick & 0x200 != 0) ratio = (ratio * 0xf987a7253ac413176f2b074cf7815e54) >> 128; + if (absTick & 0x400 != 0) ratio = (ratio * 0xf3392b0822b70005940c7a398e4b70f3) >> 128; + if (absTick & 0x800 != 0) ratio = (ratio * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128; + if (absTick & 0x1000 != 0) ratio = (ratio * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128; + if (absTick & 0x2000 != 0) ratio = (ratio * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128; + if (absTick & 0x4000 != 0) ratio = (ratio * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128; + if (absTick & 0x8000 != 0) ratio = (ratio * 0x31be135f97d08fd981231505542fcfa6) >> 128; + if (absTick & 0x10000 != 0) ratio = (ratio * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128; + if (absTick & 0x20000 != 0) ratio = (ratio * 0x5d6af8dedb81196699c329225ee604) >> 128; + if (absTick & 0x40000 != 0) ratio = (ratio * 0x2216e584f5fa1ea926041bedfe98) >> 128; + if (absTick & 0x80000 != 0) ratio = (ratio * 0x48a170391f7dc42444e8fa2) >> 128; + + if (tick > 0) ratio = type(uint256).max / ratio; + + // this divides by 1<<32 rounding up to go from a Q128.128 to a Q128.96. + // we then downcast because we know the result always fits within 160 bits due to our tick input constraint + // we round up in the division so getTickAtSqrtRatio of the output price is always consistent + sqrtPriceX96 = uint160((ratio >> 32) + (ratio % (1 << 32) == 0 ? 0 : 1)); + } + + /// @notice Calculates the greatest tick value such that getRatioAtTick(tick) <= ratio + /// @dev Throws in case sqrtPriceX96 < MIN_SQRT_RATIO, as MIN_SQRT_RATIO is the lowest value getRatioAtTick may + /// ever return. + /// @param sqrtPriceX96 The sqrt ratio for which to compute the tick as a Q64.96 + /// @return tick The greatest tick for which the ratio is less than or equal to the input ratio + function getTickAtSqrtRatio(uint160 sqrtPriceX96) internal pure returns (int24 tick) { + // second inequality must be < because the price can never reach the price at the max tick + require(sqrtPriceX96 >= MIN_SQRT_RATIO && sqrtPriceX96 < MAX_SQRT_RATIO, 'R'); + uint256 ratio = uint256(sqrtPriceX96) << 32; + + uint256 r = ratio; + uint256 msb = 0; + + assembly { + let f := shl(7, gt(r, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(6, gt(r, 0xFFFFFFFFFFFFFFFF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(5, gt(r, 0xFFFFFFFF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(4, gt(r, 0xFFFF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(3, gt(r, 0xFF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(2, gt(r, 0xF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(1, gt(r, 0x3)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := gt(r, 0x1) + msb := or(msb, f) + } + + if (msb >= 128) r = ratio >> (msb - 127); + else r = ratio << (127 - msb); + + int256 log_2 = (int256(msb) - 128) << 64; + + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(63, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(62, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(61, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(60, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(59, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(58, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(57, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(56, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(55, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(54, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(53, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(52, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(51, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(50, f)) + } + + int256 log_sqrt10001 = log_2 * 255738958999603826347141; // 128.128 number + + int24 tickLow = int24((log_sqrt10001 - 3402992956809132418596140100660247210) >> 128); + int24 tickHi = int24((log_sqrt10001 + 291339464771989622907027621153398088495) >> 128); + + tick = tickLow == tickHi ? tickLow : getSqrtRatioAtTick(tickHi) <= sqrtPriceX96 ? tickHi : tickLow; + } +} + + +// File @uniswap/v3-periphery/contracts/libraries/OracleLibrary.sol@v1.4.4 + +// Original license: SPDX_License_Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0 <0.8.0; + + + +/// @title Oracle library +/// @notice Provides functions to integrate with V3 pool oracle +library OracleLibrary { + /// @notice Calculates time-weighted means of tick and liquidity for a given Uniswap V3 pool + /// @param pool Address of the pool that we want to observe + /// @param secondsAgo Number of seconds in the past from which to calculate the time-weighted means + /// @return arithmeticMeanTick The arithmetic mean tick from (block.timestamp - secondsAgo) to block.timestamp + /// @return harmonicMeanLiquidity The harmonic mean liquidity from (block.timestamp - secondsAgo) to block.timestamp + function consult(address pool, uint32 secondsAgo) + internal + view + returns (int24 arithmeticMeanTick, uint128 harmonicMeanLiquidity) + { + require(secondsAgo != 0, 'BP'); + + uint32[] memory secondsAgos = new uint32[](2); + secondsAgos[0] = secondsAgo; + secondsAgos[1] = 0; + + (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) = + IUniswapV3Pool(pool).observe(secondsAgos); + + int56 tickCumulativesDelta = tickCumulatives[1] - tickCumulatives[0]; + uint160 secondsPerLiquidityCumulativesDelta = + secondsPerLiquidityCumulativeX128s[1] - secondsPerLiquidityCumulativeX128s[0]; + + arithmeticMeanTick = int24(tickCumulativesDelta / secondsAgo); + // Always round to negative infinity + if (tickCumulativesDelta < 0 && (tickCumulativesDelta % secondsAgo != 0)) arithmeticMeanTick--; + + // We are multiplying here instead of shifting to ensure that harmonicMeanLiquidity doesn't overflow uint128 + uint192 secondsAgoX160 = uint192(secondsAgo) * type(uint160).max; + harmonicMeanLiquidity = uint128(secondsAgoX160 / (uint192(secondsPerLiquidityCumulativesDelta) << 32)); + } + + /// @notice Given a tick and a token amount, calculates the amount of token received in exchange + /// @param tick Tick value used to calculate the quote + /// @param baseAmount Amount of token to be converted + /// @param baseToken Address of an ERC20 token contract used as the baseAmount denomination + /// @param quoteToken Address of an ERC20 token contract used as the quoteAmount denomination + /// @return quoteAmount Amount of quoteToken received for baseAmount of baseToken + function getQuoteAtTick( + int24 tick, + uint128 baseAmount, + address baseToken, + address quoteToken + ) internal pure returns (uint256 quoteAmount) { + uint160 sqrtRatioX96 = TickMath.getSqrtRatioAtTick(tick); + + // Calculate quoteAmount with better precision if it doesn't overflow when multiplied by itself + if (sqrtRatioX96 <= type(uint128).max) { + uint256 ratioX192 = uint256(sqrtRatioX96) * sqrtRatioX96; + quoteAmount = baseToken < quoteToken + ? FullMath.mulDiv(ratioX192, baseAmount, 1 << 192) + : FullMath.mulDiv(1 << 192, baseAmount, ratioX192); + } else { + uint256 ratioX128 = FullMath.mulDiv(sqrtRatioX96, sqrtRatioX96, 1 << 64); + quoteAmount = baseToken < quoteToken + ? FullMath.mulDiv(ratioX128, baseAmount, 1 << 128) + : FullMath.mulDiv(1 << 128, baseAmount, ratioX128); + } + } + + /// @notice Given a pool, it returns the number of seconds ago of the oldest stored observation + /// @param pool Address of Uniswap V3 pool that we want to observe + /// @return secondsAgo The number of seconds ago of the oldest observation stored for the pool + function getOldestObservationSecondsAgo(address pool) internal view returns (uint32 secondsAgo) { + (, , uint16 observationIndex, uint16 observationCardinality, , , ) = IUniswapV3Pool(pool).slot0(); + require(observationCardinality > 0, 'NI'); + + (uint32 observationTimestamp, , , bool initialized) = + IUniswapV3Pool(pool).observations((observationIndex + 1) % observationCardinality); + + // The next index might not be initialized if the cardinality is in the process of increasing + // In this case the oldest observation is always in index 0 + if (!initialized) { + (observationTimestamp, , , ) = IUniswapV3Pool(pool).observations(0); + } + + secondsAgo = uint32(block.timestamp) - observationTimestamp; + } + + /// @notice Given a pool, it returns the tick value as of the start of the current block + /// @param pool Address of Uniswap V3 pool + /// @return The tick that the pool was in at the start of the current block + function getBlockStartingTickAndLiquidity(address pool) internal view returns (int24, uint128) { + (, int24 tick, uint16 observationIndex, uint16 observationCardinality, , , ) = IUniswapV3Pool(pool).slot0(); + + // 2 observations are needed to reliably calculate the block starting tick + require(observationCardinality > 1, 'NEO'); + + // If the latest observation occurred in the past, then no tick-changing trades have happened in this block + // therefore the tick in `slot0` is the same as at the beginning of the current block. + // We don't need to check if this observation is initialized - it is guaranteed to be. + (uint32 observationTimestamp, int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128, ) = + IUniswapV3Pool(pool).observations(observationIndex); + if (observationTimestamp != uint32(block.timestamp)) { + return (tick, IUniswapV3Pool(pool).liquidity()); + } + + uint256 prevIndex = (uint256(observationIndex) + observationCardinality - 1) % observationCardinality; + ( + uint32 prevObservationTimestamp, + int56 prevTickCumulative, + uint160 prevSecondsPerLiquidityCumulativeX128, + bool prevInitialized + ) = IUniswapV3Pool(pool).observations(prevIndex); + + require(prevInitialized, 'ONI'); + + uint32 delta = observationTimestamp - prevObservationTimestamp; + tick = int24((tickCumulative - prevTickCumulative) / delta); + uint128 liquidity = + uint128( + (uint192(delta) * type(uint160).max) / + (uint192(secondsPerLiquidityCumulativeX128 - prevSecondsPerLiquidityCumulativeX128) << 32) + ); + return (tick, liquidity); + } + + /// @notice Information for calculating a weighted arithmetic mean tick + struct WeightedTickData { + int24 tick; + uint128 weight; + } + + /// @notice Given an array of ticks and weights, calculates the weighted arithmetic mean tick + /// @param weightedTickData An array of ticks and weights + /// @return weightedArithmeticMeanTick The weighted arithmetic mean tick + /// @dev Each entry of `weightedTickData` should represents ticks from pools with the same underlying pool tokens. If they do not, + /// extreme care must be taken to ensure that ticks are comparable (including decimal differences). + /// @dev Note that the weighted arithmetic mean tick corresponds to the weighted geometric mean price. + function getWeightedArithmeticMeanTick(WeightedTickData[] memory weightedTickData) + internal + pure + returns (int24 weightedArithmeticMeanTick) + { + // Accumulates the sum of products between each tick and its weight + int256 numerator; + + // Accumulates the sum of the weights + uint256 denominator; + + // Products fit in 152 bits, so it would take an array of length ~2**104 to overflow this logic + for (uint256 i; i < weightedTickData.length; i++) { + numerator += weightedTickData[i].tick * int256(weightedTickData[i].weight); + denominator += weightedTickData[i].weight; + } + + weightedArithmeticMeanTick = int24(numerator / int256(denominator)); + // Always round to negative infinity + if (numerator < 0 && (numerator % int256(denominator) != 0)) weightedArithmeticMeanTick--; + } + + /// @notice Returns the "synthetic" tick which represents the price of the first entry in `tokens` in terms of the last + /// @dev Useful for calculating relative prices along routes. + /// @dev There must be one tick for each pairwise set of tokens. + /// @param tokens The token contract addresses + /// @param ticks The ticks, representing the price of each token pair in `tokens` + /// @return syntheticTick The synthetic tick, representing the relative price of the outermost tokens in `tokens` + function getChainedPrice(address[] memory tokens, int24[] memory ticks) + internal + pure + returns (int256 syntheticTick) + { + require(tokens.length - 1 == ticks.length, 'DL'); + for (uint256 i = 1; i <= ticks.length; i++) { + // check the tokens for address sort order, then accumulate the + // ticks into the running synthetic tick, ensuring that intermediate tokens "cancel out" + tokens[i - 1] < tokens[i] ? syntheticTick += ticks[i - 1] : syntheticTick -= ticks[i - 1]; + } + } +} + + +// File @uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol@v1.0.1 + +// Original license: SPDX_License_Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/// @title The interface for the Uniswap V3 Factory +/// @notice The Uniswap V3 Factory facilitates creation of Uniswap V3 pools and control over the protocol fees +interface IUniswapV3Factory { + /// @notice Emitted when the owner of the factory is changed + /// @param oldOwner The owner before the owner was changed + /// @param newOwner The owner after the owner was changed + event OwnerChanged(address indexed oldOwner, address indexed newOwner); + + /// @notice Emitted when a pool is created + /// @param token0 The first token of the pool by address sort order + /// @param token1 The second token of the pool by address sort order + /// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip + /// @param tickSpacing The minimum number of ticks between initialized ticks + /// @param pool The address of the created pool + event PoolCreated( + address indexed token0, + address indexed token1, + uint24 indexed fee, + int24 tickSpacing, + address pool + ); + + /// @notice Emitted when a new fee amount is enabled for pool creation via the factory + /// @param fee The enabled fee, denominated in hundredths of a bip + /// @param tickSpacing The minimum number of ticks between initialized ticks for pools created with the given fee + event FeeAmountEnabled(uint24 indexed fee, int24 indexed tickSpacing); + + /// @notice Returns the current owner of the factory + /// @dev Can be changed by the current owner via setOwner + /// @return The address of the factory owner + function owner() external view returns (address); + + /// @notice Returns the tick spacing for a given fee amount, if enabled, or 0 if not enabled + /// @dev A fee amount can never be removed, so this value should be hard coded or cached in the calling context + /// @param fee The enabled fee, denominated in hundredths of a bip. Returns 0 in case of unenabled fee + /// @return The tick spacing + function feeAmountTickSpacing(uint24 fee) external view returns (int24); + + /// @notice Returns the pool address for a given pair of tokens and a fee, or address 0 if it does not exist + /// @dev tokenA and tokenB may be passed in either token0/token1 or token1/token0 order + /// @param tokenA The contract address of either token0 or token1 + /// @param tokenB The contract address of the other token + /// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip + /// @return pool The pool address + function getPool( + address tokenA, + address tokenB, + uint24 fee + ) external view returns (address pool); + + /// @notice Creates a pool for the given two tokens and fee + /// @param tokenA One of the two tokens in the desired pool + /// @param tokenB The other of the two tokens in the desired pool + /// @param fee The desired fee for the pool + /// @dev tokenA and tokenB may be passed in either order: token0/token1 or token1/token0. tickSpacing is retrieved + /// from the fee. The call will revert if the pool already exists, the fee is invalid, or the token arguments + /// are invalid. + /// @return pool The address of the newly created pool + function createPool( + address tokenA, + address tokenB, + uint24 fee + ) external returns (address pool); + + /// @notice Updates the owner of the factory + /// @dev Must be called by the current owner + /// @param _owner The new owner of the factory + function setOwner(address _owner) external; + + /// @notice Enables a fee amount with the given tickSpacing + /// @dev Fee amounts may never be removed once enabled + /// @param fee The fee amount to enable, denominated in hundredths of a bip (i.e. 1e-6) + /// @param tickSpacing The spacing between ticks to be enforced for all pools created with the given fee amount + function enableFeeAmount(uint24 fee, int24 tickSpacing) external; +} + + +// File contracts/interfaces/IUniswapOracleV3.sol + +// Original license: SPDX_License_Identifier: MIT +pragma solidity >=0.5.0; + +interface IUniswapOracleV3 { + function PERIOD() external returns (uint256); + + function factory() external returns (address); + + function update(address _tokenIn, address _tokenOut) external; + + function consult( + address _tokenIn, + uint256 _amountIn, + address _tokenOut + ) external view returns (uint256 _amountOut); + + function updateAndConsult( + address _tokenIn, + uint256 _amountIn, + address _tokenOut + ) external returns (uint256 _amountOut); +} + + +// File contracts/utils/UniswapOracleV3.sol + +// Original license: SPDX_License_Identifier: MIT +pragma solidity >=0.5.0; + + + +contract UniswapOracleV3 is IUniswapOracleV3 { + uint256 public constant override PERIOD = 60 * 60; // in seconds + address public immutable override factory; + uint24 FEE = 500; + + event PairAdded(address token1, address token2); + + constructor(address _factory) { + factory = _factory; + } + + function consult( + address _tokenIn, + uint256 _amountIn, + address _tokenOut + ) public view override returns (uint256 quoteAmount) { + address pool = IUniswapV3Factory(factory).getPool(_tokenIn, _tokenOut, FEE); + if (pool == address(0)) { + return 0; + } + (int24 timeWeightedAverageTick, ) = OracleLibrary.consult( + pool, + uint32(PERIOD) + ); + quoteAmount = OracleLibrary.getQuoteAtTick( + timeWeightedAverageTick, + uint128(_amountIn), + _tokenIn, + _tokenOut + ); + } + + // deprec + function update(address _tokenIn, address _tokenOut) public override { + address pool = IUniswapV3Factory(factory).getPool(_tokenIn, _tokenOut, FEE); + if (pool == address(0)) { + pool = IUniswapV3Factory(factory).createPool(_tokenIn, _tokenOut, FEE); + } + emit PairAdded(_tokenIn, _tokenOut); + } + + // deprec + function updateAndConsult( + address _tokenIn, + uint256 _amountIn, + address _tokenOut + ) external override returns (uint256 _amountOut) { + update(_tokenIn, _tokenOut); + _amountOut = consult(_tokenIn, _amountIn, _tokenOut); + } +} diff --git a/packages/contracts/src/index.ts b/packages/contracts/src/index.ts index f0446e416f1..078391ede36 100644 --- a/packages/contracts/src/index.ts +++ b/packages/contracts/src/index.ts @@ -33,11 +33,13 @@ import UnlockV9 from './abis/Unlock/UnlockV9.json' import UnlockDiscountTokenV0 from './abis/UnlockDiscountToken/UnlockDiscountTokenV0.json' import UnlockDiscountTokenV1 from './abis/UnlockDiscountToken/UnlockDiscountTokenV1.json' import UnlockDiscountTokenV2 from './abis/UnlockDiscountToken/UnlockDiscountTokenV2.json' +import UnlockDiscountTokenV3 from './abis/UnlockDiscountToken/UnlockDiscountTokenV3.json' import GovernorUnlockProtocol from './abis/Governor/UnlockProtocolGovernor.json' import GovernorUnlockProtocolTimelock from './abis/Governor/UnlockProtocolTimelock.json' import LockSerializer from './abis/utils/LockSerializer.json' import UnlockSwapPurchaser from './abis/utils/UnlockSwapPurchaser.json' import UnlockSwapBurner from './abis/utils/UnlockSwapBurner.json' +import UniswapOracleV3 from './abis/utils/UniswapOracleV3.json' // exports export { PublicLockV0 } @@ -72,6 +74,12 @@ export { UnlockV9 } export { UnlockDiscountTokenV0 } export { UnlockDiscountTokenV1 } export { UnlockDiscountTokenV2 } +export { UnlockDiscountTokenV3 } export { GovernorUnlockProtocol } export { GovernorUnlockProtocolTimelock } -export { LockSerializer, UnlockSwapPurchaser, UnlockSwapBurner } +export { + LockSerializer, + UnlockSwapPurchaser, + UnlockSwapBurner, + UniswapOracleV3, +} diff --git a/packages/contracts/src/utils/parser.ts b/packages/contracts/src/utils/parser.ts index 8e6bb0ed167..a162f821b3a 100644 --- a/packages/contracts/src/utils/parser.ts +++ b/packages/contracts/src/utils/parser.ts @@ -20,22 +20,24 @@ async function main() { console.log(`import ${contractName}${versionNumber} from '${abiPath}'`) ) - console.log("import LockSerializer from './abis/utils/LockSerializer.json'") - console.log( - "import UnlockSwapPurchaser from './abis/utils/UnlockSwapPurchaser.json'" - ) - console.log( - "import UnlockSwapBurner from './abis/utils/UnlockSwapBurner.json'" - ) + const utils = [ + 'LockSerializer', + 'UnlockSwapPurchaser', + 'UnlockSwapBurner', + 'UniswapOracleV3', + ] + + utils.forEach((util) => { + console.log(`import ${util} from './abis/utils/${util}.json'`) + }) + console.log('\n// exports') exports.forEach(({ contractName, versionNumber }) => console.log(`export { ${contractName}${versionNumber} }`) ) - console.log( - 'export { LockSerializer, UnlockSwapPurchaser, UnlockSwapBurner }' - ) + console.log(`export { ${utils.toString()} }`) } main() diff --git a/packages/email-templates/src/index.ts b/packages/email-templates/src/index.ts index 6c57f4f75e2..0cb708b4ccc 100644 --- a/packages/email-templates/src/index.ts +++ b/packages/email-templates/src/index.ts @@ -8,6 +8,7 @@ import keyAirdropped from './templates/keyAirdropped' import keyExpiring from './templates/keyExpiring' import keyExpired from './templates/keyExpired' import eventKeyMined from './templates/eventKeyMined' +import eventRsvpSubmitted from './templates/eventRsvpSubmitted' import eventKeyAirdropped from './templates/eventKeyAirdropped' import certificationKeyMined from './templates/certificationKeyMined' import certificationKeyAirdropped from './templates/certificationKeyAirdropped' @@ -31,6 +32,7 @@ type Template = | 'keyExpiring' | 'keyExpired' | 'eventKeyMined' + | 'eventRsvpSubmitted' | 'eventKeyAirdropped' | 'certificationKeyMined' | 'certificationKeyAirdropped' @@ -47,6 +49,7 @@ export const EmailTemplates: Record = { keyExpired, keyExpiring, eventKeyMined, + eventRsvpSubmitted, eventKeyAirdropped, certificationKeyMined, certificationKeyAirdropped, diff --git a/packages/email-templates/src/templates/eventRsvpSubmitted.ts b/packages/email-templates/src/templates/eventRsvpSubmitted.ts new file mode 100644 index 00000000000..71901db1acb --- /dev/null +++ b/packages/email-templates/src/templates/eventRsvpSubmitted.ts @@ -0,0 +1,26 @@ +import Handlebars from 'handlebars' +import { links } from './helpers/links' +import { transactionLink } from './helpers/transactionLink' +import { eventDetailsLight } from './helpers/eventDetails' + +Handlebars.registerHelper('links', links) +Handlebars.registerHelper('transactionLink', transactionLink) +Handlebars.registerHelper('eventDetailsLight', eventDetailsLight) + +export default { + base: 'events', + subject: `You have applied to attend {{{eventName}}}!`, + html: `

Thanks

+ +You have successfully applied to attend {{{eventName}}}. +The organizer will be in touch with you soon, and, if you are accepted, +you will receive a confirmation email with your ticket! + +{{eventDetailsLight + eventName + eventDate + eventTime + eventUrl +}} +`, +} diff --git a/packages/email-templates/src/templates/helpers/eventDetails.ts b/packages/email-templates/src/templates/helpers/eventDetails.ts index b233b7324ff..9ba2cd5a372 100644 --- a/packages/email-templates/src/templates/helpers/eventDetails.ts +++ b/packages/email-templates/src/templates/helpers/eventDetails.ts @@ -48,3 +48,33 @@ export function eventDetails( return new Handlebars.SafeString(content) } + +export function eventDetailsLight( + eventName: string, + eventDate: string, + eventTime: string, + eventUrl: string +) { + let content = ` +
+

Event details

+ +
+ ${eventName} +
+ +
+ Date: ${eventDate} +
+ +
+ Time: ${eventTime} +
+ + +
` + + return new Handlebars.SafeString(content) +} diff --git a/packages/networks/src/networks/sepolia.ts b/packages/networks/src/networks/sepolia.ts index 71dedd4394f..922c9d2c6cb 100644 --- a/packages/networks/src/networks/sepolia.ts +++ b/packages/networks/src/networks/sepolia.ts @@ -102,6 +102,9 @@ export const sepolia: NetworkConfig = { universalRouterAddress: '0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD', }, unlockAddress: '0x36b34e10295cCE69B652eEB5a8046041074515Da', + unlockDaoToken: { + address: '0x447B1492C5038203f1927eB2a374F5Fcdc25999d', + }, url: 'https://github.com/eth-clients/sepolia', } diff --git a/packages/unlock-js/CHANGELOG.md b/packages/unlock-js/CHANGELOG.md index a2c3ebe673f..c8a3d3be447 100644 --- a/packages/unlock-js/CHANGELOG.md +++ b/packages/unlock-js/CHANGELOG.md @@ -1,5 +1,9 @@ # Changes +# 0.43.0 + +- allowing `maxNumberOfKeys` to be 0 when deploying locks. + # 0.42.0 - added support for `UnlockV13` and `PublicLockV14` diff --git a/packages/unlock-js/openapi.yml b/packages/unlock-js/openapi.yml index 3ca28de602f..148ce93454a 100644 --- a/packages/unlock-js/openapi.yml +++ b/packages/unlock-js/openapi.yml @@ -3426,3 +3426,52 @@ paths: 500: $ref: '#/components/responses/500.InternalError' + + /v2/rsvp/{network}/{lockAddress}: + post: + operationId: rsvp + parameters: + - $ref: '#/components/parameters/Network' + - $ref: '#/components/parameters/LockAddress' + - name: captcha + in: header + required: true + description: Recaptcha value to pass. + schema: + type: string + + description: User applies to attend an event. + requestBody: + content: + application/json: + schema: + type: object + properties: + recipient: + type: string + email: + type: string + data: + type: object + additionalProperties: true + responses: + 200: + description: Successfully applied to event + content: + application/json: + schema: + type: object + properties: + lockAddress: + type: string + userAddress: + type: string + approval: + type: string + network: + type: number + 400: + $ref: '#/components/responses/400.Invalid' + + 500: + $ref: '#/components/responses/500.InternalError' diff --git a/packages/unlock-js/package.json b/packages/unlock-js/package.json index 3b755e08a6f..5367dcfc062 100644 --- a/packages/unlock-js/package.json +++ b/packages/unlock-js/package.json @@ -1,6 +1,6 @@ { "name": "@unlock-protocol/unlock-js", - "version": "0.42.0", + "version": "0.43.0", "description": "This module provides libraries to include Unlock APIs inside a Javascript application.", "main": "dist/index.js", "module": "dist/index.mjs", diff --git a/packages/unlock-js/src/KeyManager.ts b/packages/unlock-js/src/KeyManager.ts index 30bf3c62b68..db956790058 100644 --- a/packages/unlock-js/src/KeyManager.ts +++ b/packages/unlock-js/src/KeyManager.ts @@ -176,7 +176,9 @@ export class KeyManager { email: params.email.trim().toLowerCase(), lock: params.lockAddress.trim().toLowerCase(), } - return ethers.utils.id(JSON.stringify(item)).slice(0, 42) + return ethers.utils.getAddress( + ethers.utils.id(JSON.stringify(item)).slice(0, 42) + ) } /** diff --git a/packages/unlock-js/src/Unlock/v10/createLock.js b/packages/unlock-js/src/Unlock/v10/createLock.js index 0b145d56428..1156783bf76 100644 --- a/packages/unlock-js/src/Unlock/v10/createLock.js +++ b/packages/unlock-js/src/Unlock/v10/createLock.js @@ -11,7 +11,10 @@ import { _getKeyPrice } from '../utils' export default async function (lock, transactionOptions = {}, callback) { const unlockContract = await this.getUnlockContract() let { maxNumberOfKeys, expirationDuration } = lock - if (!maxNumberOfKeys || maxNumberOfKeys === UNLIMITED_KEYS_COUNT) { + if ( + typeof maxNumberOfKeys !== 'number' || + maxNumberOfKeys === UNLIMITED_KEYS_COUNT + ) { maxNumberOfKeys = ETHERS_MAX_UINT } if (expirationDuration === -1) { diff --git a/packages/unlock-js/src/Unlock/v11/createLock.js b/packages/unlock-js/src/Unlock/v11/createLock.js index dbd9a7743f6..ea5cf56f969 100644 --- a/packages/unlock-js/src/Unlock/v11/createLock.js +++ b/packages/unlock-js/src/Unlock/v11/createLock.js @@ -15,7 +15,10 @@ export default async function (lock, transactionOptions = {}, callback) { lock.publicLockVersion || (await unlockContract.publicLockLatestVersion()) let { maxNumberOfKeys, expirationDuration } = lock - if (!maxNumberOfKeys || maxNumberOfKeys === UNLIMITED_KEYS_COUNT) { + if ( + typeof maxNumberOfKeys !== 'number' || + maxNumberOfKeys === UNLIMITED_KEYS_COUNT + ) { maxNumberOfKeys = ETHERS_MAX_UINT } if (expirationDuration === -1) { diff --git a/packages/unlock-js/src/Unlock/v4/createLock.js b/packages/unlock-js/src/Unlock/v4/createLock.js index 25b90dc2f8d..fde5179306d 100644 --- a/packages/unlock-js/src/Unlock/v4/createLock.js +++ b/packages/unlock-js/src/Unlock/v4/createLock.js @@ -12,7 +12,10 @@ import { _getKeyPrice } from '../utils' export default async function (lock, transactionOptions = {}, callback) { const unlockContract = await this.getUnlockContract() let { maxNumberOfKeys } = lock - if (!maxNumberOfKeys || maxNumberOfKeys === UNLIMITED_KEYS_COUNT) { + if ( + typeof maxNumberOfKeys !== 'number' || + maxNumberOfKeys === UNLIMITED_KEYS_COUNT + ) { maxNumberOfKeys = ETHERS_MAX_UINT } diff --git a/packages/unlock-js/src/Unlock/v6/createLock.js b/packages/unlock-js/src/Unlock/v6/createLock.js index 225325acc74..f52d283e5fc 100644 --- a/packages/unlock-js/src/Unlock/v6/createLock.js +++ b/packages/unlock-js/src/Unlock/v6/createLock.js @@ -12,7 +12,10 @@ import { _getKeyPrice } from '../utils' export default async function (lock, transactionOptions = {}, callback) { const unlockContract = await this.getUnlockContract() let { maxNumberOfKeys } = lock - if (!maxNumberOfKeys || maxNumberOfKeys === UNLIMITED_KEYS_COUNT) { + if ( + typeof maxNumberOfKeys !== 'number' || + maxNumberOfKeys === UNLIMITED_KEYS_COUNT + ) { maxNumberOfKeys = ETHERS_MAX_UINT } diff --git a/packages/unlock-js/src/Unlock/v7/createLock.js b/packages/unlock-js/src/Unlock/v7/createLock.js index c8c4073b8f7..c501d9bc0b4 100644 --- a/packages/unlock-js/src/Unlock/v7/createLock.js +++ b/packages/unlock-js/src/Unlock/v7/createLock.js @@ -12,7 +12,10 @@ import { _getKeyPrice } from '../utils' export default async function (lock, transactionOptions = {}, callback) { const unlockContract = await this.getUnlockContract() let { maxNumberOfKeys } = lock - if (!maxNumberOfKeys || maxNumberOfKeys === UNLIMITED_KEYS_COUNT) { + if ( + typeof maxNumberOfKeys !== 'number' || + maxNumberOfKeys === UNLIMITED_KEYS_COUNT + ) { maxNumberOfKeys = ETHERS_MAX_UINT } diff --git a/packages/unlock-js/src/Unlock/v8/createLock.js b/packages/unlock-js/src/Unlock/v8/createLock.js index e5aa2e9a798..909dbfcf31a 100644 --- a/packages/unlock-js/src/Unlock/v8/createLock.js +++ b/packages/unlock-js/src/Unlock/v8/createLock.js @@ -12,7 +12,10 @@ import { _getKeyPrice } from '../utils' export default async function (lock, transactionOptions = {}, callback) { const unlockContract = await this.getUnlockContract() let { maxNumberOfKeys } = lock - if (!maxNumberOfKeys || maxNumberOfKeys === UNLIMITED_KEYS_COUNT) { + if ( + typeof maxNumberOfKeys !== 'number' || + maxNumberOfKeys === UNLIMITED_KEYS_COUNT + ) { maxNumberOfKeys = ETHERS_MAX_UINT } diff --git a/packages/unlock-js/src/abis.ts b/packages/unlock-js/src/abis.ts index 9054e1a2c1a..a8134c6b676 100644 --- a/packages/unlock-js/src/abis.ts +++ b/packages/unlock-js/src/abis.ts @@ -1217,7 +1217,7 @@ const abis = { 'function withdraw(address _tokenAddress,address _recipient,uint256 _amount)', ], bytecodeHash: - '0xccbff9b917e3e99d7b38c1a6c0e5ef0786f4708a62bcf486c2490c93555e46f9', + '0x349cbd03803a9577581041659dfa1d832ca72d580bdb998e38907e98f20ad649', }, }, Unlock: { @@ -1632,7 +1632,7 @@ const abis = { 'function weth() view returns (address)', ], bytecodeHash: - '0xccc4254b5847956ab3f1ca8a19a2af059792a4b470dfb648dd9407fefa4a6bd6', + '0x091d4f41845ea7a4880d62d7068ea5c85128b9ed2879d7ae0402ae7b6b2d3f8f', }, }, } diff --git a/scripts/manual-production-pr.sh b/scripts/manual-production-pr.sh index af0daf5f87a..f0693b2d3d6 100755 --- a/scripts/manual-production-pr.sh +++ b/scripts/manual-production-pr.sh @@ -33,13 +33,13 @@ echo "This command will open a pull request to deploy $COMMIT_TO_DEPLOY to produ LATEST_PRODUCTION_COMMIT_MESSAGE=`git log -1 origin/production --pretty=%B` COMMIT_REGEX="commit:([0-9a-f]{40})" -# if [[ $LATEST_PRODUCTION_COMMIT_MESSAGE =~ $COMMIT_REGEX ]] -# then +if [[ $LATEST_PRODUCTION_COMMIT_MESSAGE =~ $COMMIT_REGEX ]] +then LATEST_COMMIT_ID_IN_PRODUCTION="${BASH_REMATCH[1]}" -# else -# echo "Skipping automated deployment. Latest production does not include commit sha1 (this is to avoid deploying an older version of master)." -# exit 0 -# fi +else + echo "Skipping automated deployment. Latest production does not include commit sha1 (this is to avoid deploying an older version of master)." + exit 0 +fi LATEST_PRODUCTION_TIMESTAMP=`git show -s --format=%ct $LATEST_COMMIT_ID_IN_PRODUCTION` diff --git a/unlock-app/src/components/content/event/EventDetails.tsx b/unlock-app/src/components/content/event/EventDetails.tsx index 85ddb4c8943..2e7e4c8b192 100644 --- a/unlock-app/src/components/content/event/EventDetails.tsx +++ b/unlock-app/src/components/content/event/EventDetails.tsx @@ -27,7 +27,7 @@ import { ToastHelper } from '~/components/helpers/toast.helper' import { CoverImageDrawer } from './CoverImageDrawer' import { EventDetail } from './EventDetail' import { EventLocation } from './EventLocation' -import { RegistrationCard } from './RegistrationCard' +import { RegistrationCard } from './Registration/RegistrationCard' import { useEvent } from '~/hooks/useEvent' import { SettingEmail } from '~/components/interface/locks/Settings/elements/SettingEmail' import { storage } from '~/config/storage' @@ -280,7 +280,7 @@ export const EventDetails = ({ - + diff --git a/unlock-app/src/components/content/event/Form.tsx b/unlock-app/src/components/content/event/Form.tsx index 13a67bf4605..709ac3c6d19 100644 --- a/unlock-app/src/components/content/event/Form.tsx +++ b/unlock-app/src/components/content/event/Form.tsx @@ -73,6 +73,7 @@ export const Form = ({ onSubmit }: FormProps) => { const { networks } = useConfig() const { network, account } = useAuth() const [isInPerson, setIsInPerson] = useState(true) + const [screeningEnabled, enableScreening] = useState(false) const [isUnlimitedCapacity, setIsUnlimitedCapacity] = useState(false) const [isFree, setIsFree] = useState(true) const [isCurrencyModalOpen, setCurrencyModalOpen] = useState(false) @@ -441,103 +442,125 @@ export const Form = ({ onSubmit }: FormProps) => { - -
+ +

- These settings can also be changed, but only by sending on-chain - transactions. + Enable this feature so guest can apply to attend your event & + get your approval before receiving the NFT ticket.

-
-
+ { + if (enabled) { + setValue('lock.maxNumberOfKeys', 0) + } + }} + /> +
+ + + {!screeningEnabled && ( + +
+

+ These settings can also be changed, but only by sending + on-chain transactions. +

+ +
+
+ + { + if (enable) { + setValue('lock.keyPrice', '0') + } + }} + /> +
+ +
+ { + setValue('lock.currencyContractAddress', token.address) + setValue('currencySymbol', token.symbol) + }} + /> +
+
+
setCurrencyModalOpen(true)} + className="box-border flex items-center flex-1 w-full gap-2 pl-4 text-base text-left transition-all border border-gray-400 rounded-lg shadow-sm cursor-pointer hover:border-gray-500 focus:ring-gray-500 focus:border-gray-500 focus:outline-none" + > + + {details.currencySymbol} +
+
+
+ + +
+
+
+ +
{ - if (enable) { - setValue('lock.keyPrice', '0') + title="Unlimited" + enabled={isUnlimitedCapacity} + setEnabled={setIsUnlimitedCapacity} + onChange={(enabled) => { + if (enabled) { + setValue('lock.maxNumberOfKeys', undefined) } }} />
-
- { - setValue('lock.currencyContractAddress', token.address) - setValue('currencySymbol', token.symbol) - }} - /> -
-
-
setCurrencyModalOpen(true)} - className="box-border flex items-center flex-1 w-full gap-2 pl-4 text-base text-left transition-all border border-gray-400 rounded-lg shadow-sm cursor-pointer hover:border-gray-500 focus:ring-gray-500 focus:border-gray-500 focus:outline-none" - > - - {details.currencySymbol} -
-
-
- - -
-
-
- -
- - { - if (enabled) { - setValue('lock.maxNumberOfKeys', undefined) - } - }} +
- -
- + + )}
{Object.keys(errors).length > 0 && ( diff --git a/unlock-app/src/components/content/event/Registration/EmbeddedCheckout.tsx b/unlock-app/src/components/content/event/Registration/EmbeddedCheckout.tsx new file mode 100644 index 00000000000..fbf59b70b55 --- /dev/null +++ b/unlock-app/src/components/content/event/Registration/EmbeddedCheckout.tsx @@ -0,0 +1,35 @@ +import { Button, Modal } from '@unlock-protocol/ui' +import { Checkout } from '~/components/interface/checkout/main' +import { useState } from 'react' +import { selectProvider } from '~/hooks/useAuthenticate' +import { useConfig } from '~/utils/withConfig' + +export const EmbeddedCheckout = ({ checkoutConfig, refresh }: any) => { + const [isCheckoutOpen, setCheckoutOpen] = useState(false) + const config = useConfig() + const injectedProvider = selectProvider(config) + + return ( + <> + + { + refresh() + setCheckoutOpen(false) + }} + /> + + + + ) +} diff --git a/unlock-app/src/components/content/event/Registration/HasTicket.tsx b/unlock-app/src/components/content/event/Registration/HasTicket.tsx new file mode 100644 index 00000000000..b976ec7392a --- /dev/null +++ b/unlock-app/src/components/content/event/Registration/HasTicket.tsx @@ -0,0 +1,16 @@ +import { Card } from '@unlock-protocol/ui' +import Link from 'next/link' + +export const HasTicket = () => { + return ( + +

+ 🎉 You already have a ticket! You can view it in{' '} + + your keychain + + . +

+
+ ) +} diff --git a/unlock-app/src/components/content/event/LockPriceDetails.tsx b/unlock-app/src/components/content/event/Registration/LockPriceDetails.tsx similarity index 75% rename from unlock-app/src/components/content/event/LockPriceDetails.tsx rename to unlock-app/src/components/content/event/Registration/LockPriceDetails.tsx index c74011ad916..0e231a894ff 100644 --- a/unlock-app/src/components/content/event/LockPriceDetails.tsx +++ b/unlock-app/src/components/content/event/Registration/LockPriceDetails.tsx @@ -5,14 +5,65 @@ import { useGetLockCurrencySymbol } from '~/hooks/useSymbol' import { HiOutlineTicket as TicketIcon } from 'react-icons/hi' import { ExplorerLink } from '~/components/interface/AddressLink' import { Icon, Placeholder } from '@unlock-protocol/ui' +import { Lock } from '~/unlockTypes' + +interface LockPriceInternalsProps { + lock: Lock + symbol: string + price: string + network: number + hasUnlimitedKeys: boolean + isSoldOut: boolean + keysLeft: number + showContract?: boolean +} +export const LockPriceInternals = ({ + lock, + network, + symbol, + price, + hasUnlimitedKeys, + isSoldOut, + keysLeft, + showContract = false, +}: LockPriceInternalsProps) => { + return ( +
+ {showContract && ( +
+ {lock!.name} +
+ )} +
+
+ <> + {symbol && } + + {price} {price === 'FREE' ? '' : symbol} + + +
+
+ + {hasUnlimitedKeys ? ( + + ) : ( + + {isSoldOut ? 'Sold out' : keysLeft} + + )} + {!isSoldOut && Left} +
+
+
+ ) +} interface LockPriceDetailsProps { lockAddress: string network: number - metadata?: any showContract?: boolean } - export const LockPriceDetails = ({ lockAddress, network, @@ -49,33 +100,15 @@ export const LockPriceDetails = ({ } return ( -
- {showContract && ( -
- {lock!.name} -
- )} -
-
- <> - {symbol && } - - {price} {price === 'FREE' ? '' : symbol} - - -
-
- - {hasUnlimitedKeys ? ( - - ) : ( - - {isSoldOut ? 'Sold out' : keysLeft} - - )} - {!isSoldOut && Left} -
-
-
+ ) } diff --git a/unlock-app/src/components/content/event/Registration/RegistrationCard.tsx b/unlock-app/src/components/content/event/Registration/RegistrationCard.tsx new file mode 100644 index 00000000000..8c10e0b19ba --- /dev/null +++ b/unlock-app/src/components/content/event/Registration/RegistrationCard.tsx @@ -0,0 +1,70 @@ +import { Placeholder } from '@unlock-protocol/ui' +import { LockPriceDetails } from './LockPriceDetails' +import { Card } from '@unlock-protocol/ui' +import { PaywallConfigType } from '@unlock-protocol/core' +import { RegistrationCardSingleLock } from './SingleLock' +import { useValidKeyBulk } from '~/hooks/useKey' +import { HasTicket } from './HasTicket' +import { EmbeddedCheckout } from './EmbeddedCheckout' + +export interface RegistrationCardProps { + checkoutConfig: { + id?: string + config: PaywallConfigType + } +} + +export const RegistrationCard = ({ checkoutConfig }: RegistrationCardProps) => { + // Check if the user has a key! + const queries = useValidKeyBulk(checkoutConfig.config.locks) + const refresh = () => { + queries.map((query) => query.refetch()) + } + + const isLoadingValidKeys = queries?.some( + (query) => query.isInitialLoading || query.isRefetching + ) + const hasValidKey = queries?.map((query) => query.data).some((value) => value) + + if (isLoadingValidKeys) { + return + } + + if (hasValidKey) { + return + } + + // We need to behave differently if there is only one lock + // If there is one single lock + if (Object.keys(checkoutConfig.config.locks).length == 1) { + return ( + + + + ) + } + + // Multiple locks! + return ( + + {Object.keys(checkoutConfig.config.locks)?.map((lockAddress: string) => { + return ( + + ) + })} + + + ) +} diff --git a/unlock-app/src/components/content/event/Registration/SingleLock/index.tsx b/unlock-app/src/components/content/event/Registration/SingleLock/index.tsx new file mode 100644 index 00000000000..93d5a6638d8 --- /dev/null +++ b/unlock-app/src/components/content/event/Registration/SingleLock/index.tsx @@ -0,0 +1,129 @@ +import { useCanClaim } from '~/hooks/useCanClaim' +import { + WalletlessRegistrationApply, + WalletlessRegistrationClaim, +} from '../WalletlessRegistration' +import { useAuth } from '~/contexts/AuthenticationContext' +import { ZERO } from '~/components/interface/locks/Create/modals/SelectCurrencyModal' +import { LockPriceInternals } from '../LockPriceDetails' +import { Placeholder } from '@unlock-protocol/ui' +import { useGetLockCurrencySymbol } from '~/hooks/useSymbol' +import { UNLIMITED_KEYS_COUNT } from '~/constants' +import { useLockData } from '~/hooks/useLockData' +import { EmbeddedCheckout } from '../EmbeddedCheckout' +import { PaywallConfigType } from '@unlock-protocol/core' +import { emailInput } from '~/components/interface/checkout/main/Metadata' + +export interface RegistrationCardSingleLockProps { + checkoutConfig: { + id?: string + config: PaywallConfigType + } + refresh: any +} + +export const RegistrationCardSingleLock = ({ + checkoutConfig, + refresh, +}: RegistrationCardSingleLockProps) => { + const lockAddress = Object.keys(checkoutConfig.config.locks)[0] + const network = (checkoutConfig.config.locks[lockAddress].network || + checkoutConfig.config.network)! + + const { lock, isLockLoading } = useLockData({ + lockAddress, + network, + }) + + const { account } = useAuth() + + const { isInitialLoading: isClaimableLoading, data: canClaim } = useCanClaim( + { + recipients: [account || ZERO], + lockAddress, + network, + data: [], + }, + { enabled: Object.keys(checkoutConfig.config.locks).length === 1 } + ) + + const price = + lock?.keyPrice && parseFloat(lock?.keyPrice) === 0 ? 'FREE' : lock?.keyPrice + + const keysLeft = + Math.max(lock?.maxNumberOfKeys || 0, 0) - (lock?.outstandingKeys || 0) + + const hasUnlimitedKeys = lock?.maxNumberOfKeys === UNLIMITED_KEYS_COUNT + + const isSoldOut = keysLeft === 0 && !hasUnlimitedKeys + + const isClaimable = canClaim && !isSoldOut + + const { data: symbol } = useGetLockCurrencySymbol({ + lockAddress, + network, + contractAddress: lock?.currencyContractAddress, + }) + + const requiresApproval = lock?.maxNumberOfKeys == 0 + + if (isLockLoading || isClaimableLoading || !lock) { + return ( + + + + + + ) + } + + const metadataInputs = + checkoutConfig.config.metadataInputs || + checkoutConfig.config.locks[lockAddress].metadataInputs + const emailRequired = + checkoutConfig.config.emailRequired || + checkoutConfig.config.locks[lockAddress].emailRequired + + return ( + <> + {!requiresApproval && ( + + )} + {requiresApproval && ( + + )} + {!isSoldOut && isClaimable && ( + + )} + {!isSoldOut && !isClaimable && ( + + )} + + ) +} diff --git a/unlock-app/src/components/content/event/WalletlessRegistration.tsx b/unlock-app/src/components/content/event/Registration/WalletlessRegistration.tsx similarity index 57% rename from unlock-app/src/components/content/event/WalletlessRegistration.tsx rename to unlock-app/src/components/content/event/Registration/WalletlessRegistration.tsx index dc6a790bbc7..0ff4dd80931 100644 --- a/unlock-app/src/components/content/event/WalletlessRegistration.tsx +++ b/unlock-app/src/components/content/event/Registration/WalletlessRegistration.tsx @@ -20,9 +20,10 @@ import { ToastHelper } from '~/components/helpers/toast.helper' import { TransactionStatus } from '~/components/interface/checkout/main/checkoutMachine' import { onResolveName } from '~/utils/resolvers' import { RiCloseLine as CloseIcon } from 'react-icons/ri' +import { MetadataInputType } from '@unlock-protocol/core' +import { useRsvp } from '~/hooks/useRsvp' +import { IoWarningOutline } from 'react-icons/io5' -// TODO: once we have saved checkout config, use the metadata fields from there. -// In the meantime, use email + wallet address const rsvpForm = z.object({ email: z .string({ @@ -51,9 +52,10 @@ interface WalletlessRegistrationProps { } interface FormProps { + metadataInputs?: MetadataInputType[] lockAddress: string network: number - refresh: () => void + refresh?: () => void } const WalletlessRegistrationClaiming = ({ @@ -137,28 +139,142 @@ const WalletlessRegistrationClaiming = ({ ) } -export const WalletlessRegistrationForm = ({ +export const WalletlessRegistrationClaim = ({ + metadataInputs, lockAddress, network, refresh, }: FormProps) => { const [claimResult, setClaimResult] = useState() const [isClaimOpen, setClaimOpen] = useState(false) - const config = useConfig() - const recaptchaRef = useRef() - const [loading, setLoading] = useState(false) - const { account } = useAuth() const { mutateAsync: claim } = useClaim({ lockAddress, network, }) + const onRSVP = async ({ + email, + recipient, + data, + captcha, + }: { + email: string + recipient: string + data: any + captcha: string + }) => { + const { hash, owner, message } = await claim({ + metadata: data, + email, + recipient, + captcha, + }) + if (message) { + ToastHelper.error(message) + } + if (hash && owner) { + setClaimResult({ hash, owner }) + setClaimOpen(true) + ToastHelper.success('Transaction successfully sent!') + } + } + + return ( + <> + + { + setClaimOpen(false) + if (refresh) { + refresh() + } + }} + claimResult={claimResult} + /> + + + + ) +} + +export const WalletlessRegistrationApply = ({ + metadataInputs, + lockAddress, + network, +}: FormProps) => { + const { mutateAsync: rsvp } = useRsvp({ + lockAddress, + network, + }) + + const onRSVP = async ({ + email, + recipient, + captcha, + data, + }: { + email: string + recipient: string + data: any + captcha: string + }) => { + const result = await rsvp({ + data, + email, + recipient, + captcha, + }) + if (result.message) { + ToastHelper.error(result.message) + } + ToastHelper.success( + 'Application successfully sent! The organizer will contact you if you are accepted!' + ) + } + + return ( + <> +
+ +

+ This event requires a vetting process, please submit the following + form & wait for the approval by the organizers! +

+
+ + + ) +} + +export const RegistrationForm = ({ + onRSVP, + metadataInputs, +}: { + metadataInputs?: MetadataInputType[] + onRSVP: ({ + email, + recipient, + captcha, + data, + }: { + email: string + recipient: string + data: any + captcha: string + }) => void +}) => { + const config = useConfig() + const recaptchaRef = useRef() + const [loading, setLoading] = useState(false) + const { account } = useAuth() + const localForm = useForm({ mode: 'onChange', defaultValues: { email: '', recipient: account || '', - fullname: '', }, }) const { @@ -174,28 +290,21 @@ export const WalletlessRegistrationForm = ({ control, }) - const onSubmit = async ({ email, recipient, fullname }: RsvpFormProps) => { + const onSubmit = async ({ email, recipient, ...data }: RsvpFormProps) => { setLoading(true) + console.log(data) try { await recaptchaRef.current?.reset() const captcha = await recaptchaRef.current?.executeAsync() - const { hash, owner, message } = await claim({ - metadata: { - fullname, - }, + await onRSVP({ email, recipient, + data, captcha, }) - if (message) { - ToastHelper.error(message) - } - if (hash && owner) { - setClaimResult({ hash, owner }) - setClaimOpen(true) - ToastHelper.success('Transaction successfully sent!') - } + reset() } catch (error: any) { + console.error(error) ToastHelper.error( 'There was an error during registration. Please try again.' ) @@ -206,57 +315,80 @@ export const WalletlessRegistrationForm = ({ return (
- - { - setClaimOpen(false) - refresh() - reset() - }} - claimResult={claimResult} - /> - - - + + {(!metadataInputs || metadataInputs.length === 0) && ( + <> + + + + )} + + {metadataInputs?.map((metadataInputItem: any) => { + const { + name, + label, + defaultValue, + placeholder, + type, + required, + value, + } = metadataInputItem ?? {} + const inputLabel = label || name + return ( + '. + error={errors[name]?.message} + {...register(name, { + required: required && `${inputLabel} is required`, + value, + })} + /> + ) + })} + + diff --git a/unlock-app/src/components/content/event/RegistrationCard.tsx b/unlock-app/src/components/content/event/RegistrationCard.tsx deleted file mode 100644 index 21c89369f5f..00000000000 --- a/unlock-app/src/components/content/event/RegistrationCard.tsx +++ /dev/null @@ -1,137 +0,0 @@ -import { useCanClaim } from '~/hooks/useCanClaim' -import { WalletlessRegistrationForm } from './WalletlessRegistration' -import { useValidKeyBulk } from '~/hooks/useKey' -import { useAuth } from '~/contexts/AuthenticationContext' -import { ZERO } from '~/components/interface/locks/Create/modals/SelectCurrencyModal' -import { LockPriceDetails } from './LockPriceDetails' -import { Button, Card, Placeholder, Modal } from '@unlock-protocol/ui' -import Link from 'next/link' -import { Checkout } from '~/components/interface/checkout/main' -import { useState } from 'react' -import { selectProvider } from '~/hooks/useAuthenticate' -import { useConfig } from '~/utils/withConfig' -import { Event, PaywallConfigType } from '@unlock-protocol/core' - -interface RegistrationCardProps { - event: Event - checkoutConfig: { - id?: string - config: PaywallConfigType - } -} - -export const RegistrationCardInternal = ({ - checkoutConfig, -}: RegistrationCardProps) => { - const [isCheckoutOpen, setCheckoutOpen] = useState(false) - const { account } = useAuth() - const config = useConfig() - const injectedProvider = selectProvider(config) - - const queries = useValidKeyBulk(checkoutConfig.config.locks) - const refresh = () => { - queries.map((query) => query.refetch()) - } - - const isLoadingValidKeys = queries?.some( - (query) => query.isInitialLoading || query.isRefetching - ) - const hasValidKey = queries?.map((query) => query.data).some((value) => value) - - const { isInitialLoading: isClaimableLoading, data: isClaimable } = - useCanClaim( - { - recipients: [account || ZERO], - lockAddress: Object.keys(checkoutConfig.config.locks)[0], - network: (checkoutConfig.config.locks[ - Object.keys(checkoutConfig.config.locks)[0] - ].network || checkoutConfig.config.network)!, - data: [], - }, - { enabled: Object.keys(checkoutConfig.config.locks).length === 1 } - ) - - if (isLoadingValidKeys || isClaimableLoading) { - return - } - - if (hasValidKey) { - return ( -

- 🎉 You already have a ticket! You can view it in{' '} - - your keychain - - . -

- ) - } - - if (isClaimable) { - return ( - - ) - } - - return ( - <> - - { - refresh() - setCheckoutOpen(false) - }} - /> - - - - ) -} - -export const RegistrationCard = ({ - event, - checkoutConfig, -}: RegistrationCardProps) => { - return ( - -
- {Object.keys(checkoutConfig.config.locks)?.map( - (lockAddress: string) => { - return ( - - ) - } - )} -
- -
- ) -} diff --git a/unlock-app/src/components/interface/checkout/main/Captcha.tsx b/unlock-app/src/components/interface/checkout/main/Captcha.tsx index bf533c7b104..7c7b006d119 100644 --- a/unlock-app/src/components/interface/checkout/main/Captcha.tsx +++ b/unlock-app/src/components/interface/checkout/main/Captcha.tsx @@ -61,10 +61,6 @@ export function Captcha({ injectedProvider, checkoutService }: Props) {
- {/* - todo: Type '{}' is not assignable to type 'ReactNode'. - - @ts-ignore */} setRecaptchaValue(token)} diff --git a/unlock-app/src/components/interface/checkout/main/Metadata.tsx b/unlock-app/src/components/interface/checkout/main/Metadata.tsx index 4e9b199781f..968ab964a73 100644 --- a/unlock-app/src/components/interface/checkout/main/Metadata.tsx +++ b/unlock-app/src/components/interface/checkout/main/Metadata.tsx @@ -251,7 +251,7 @@ export const MetadataInputs = ({ ) } -const emailInput: MetadataInput = { +export const emailInput: MetadataInput = { type: 'email', name: 'email', label: 'Email', diff --git a/unlock-app/src/hooks/useRsvp.ts b/unlock-app/src/hooks/useRsvp.ts new file mode 100644 index 00000000000..6ba40fd688a --- /dev/null +++ b/unlock-app/src/hooks/useRsvp.ts @@ -0,0 +1,37 @@ +import { useMutation } from '@tanstack/react-query' +import { storage } from '~/config/storage' + +interface RsvpOption { + data?: any + email?: string + captcha: string + recipient?: string +} + +interface Options { + lockAddress: string + network: number +} +export const useRsvp = ({ lockAddress, network }: Options) => { + return useMutation( + ['rsvp', network, lockAddress], + async ({ data, recipient, captcha, email }: RsvpOption) => { + try { + const response = await storage.rsvp(network, lockAddress, captcha, { + recipient, + data, + email, + }) + return response.data + } catch (error: any) { + if (error.response.data?.message) { + return error.response.data + } + throw error + } + }, + { + retry: 2, + } + ) +}