diff --git a/.gitignore b/.gitignore index 05f5c0ef..6030edd4 100644 --- a/.gitignore +++ b/.gitignore @@ -41,20 +41,8 @@ facetsdeployed.txt .DS_Store bin/ -.idea/.gitignore -.idea/contracts-next.iml -.idea/misc.xml -.idea/modules.xml -.idea/runConfigurations.xml -.idea/vcs.xml -.metals -.supermaven broadcast/**/31337/* - -# subgraph artifact -NaymsDiamond.json - broadcast/**/dry-run # coverage report @@ -64,5 +52,3 @@ cov-html # gemforge.deployments.json .gemforge/*.json -# Generated files -script/deployment/S03UpgradeDiamond.s.sol diff --git a/.gitmodules b/.gitmodules index 9870147f..34428133 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,15 +7,6 @@ [submodule "lib/solmate"] path = lib/solmate url = https://github.com/transmissions11/solmate -[submodule "lib/solidity-lib"] - path = lib/solidity-lib - url = https://github.com/uniswap/solidity-lib -[submodule "lib/v3-periphery-foundry"] - path = lib/v3-periphery-foundry - url = https://github.com/gakonst/v3-periphery-foundry -[submodule "lib/solidity-stringutils"] - path = lib/solidity-stringutils - url = https://github.com/arachnid/solidity-stringutils [submodule "lib/oz"] path = lib/oz url = https://github.com/openzeppelin/openzeppelin-contracts diff --git a/.solhint.json b/.solhint.json index 186b2ec1..a3b53897 100644 --- a/.solhint.json +++ b/.solhint.json @@ -9,6 +9,7 @@ "const-name-snakecase": "off", "reason-string": "off", "var-name-mixedcase": "off", - "func-name-mixedcase": "off" + "func-name-mixedcase": "off", + "immutable-vars-naming": "off" } } diff --git a/Makefile b/Makefile index 2bf249e5..d0452963 100644 --- a/Makefile +++ b/Makefile @@ -63,7 +63,7 @@ gasforksnap: ## gas snapshot mainnet fork forge snapshot --snap .gas-snapshot \ -f ${ETH_MAINNET_RPC_URL} \ --fork-block-number 15078000 - + gasforkcheck: ## gas check mainnet fork forge snapshot --check \ -f ${ETH_MAINNET_RPC_URL} \ @@ -82,9 +82,6 @@ cov: ## coverage report -vvv coverage: ## coverage report (lcov), filtered for CI forge coverage --no-match-test testFork -vvv --report lcov --via-ir && node ./cli-tools/filter-lcov.js -lcov: ## coverage report (lcov) - forge coverage --report lcov --via-ir - gencov: ## generate html coverage report forge coverage --report lcov && genhtml -o cov-html --branch-coverage lcov.info @@ -139,9 +136,6 @@ anvil-docker: ## run anvil in a container ghcr.io/nayms/contracts-builder:latest \ -c "cd nayms && make anvil" -anvil-dbg: ## run anvil in debug mode with shared wallet - RUST_LOG=backend,api,node,rpc=warn anvil --host 0.0.0.0 --chain-id 31337 -m "${shell cat ./nayms_mnemonic.txt}" --state anvil.json - fork-mainnet: ## fork mainnet locally with anvil anvil -f ${ETH_MAINNET_RPC_URL} --accounts 20 -m "${shell cat ./nayms_mnemonic.txt}" @@ -208,72 +202,9 @@ create-entity: ## create an entity on the Nayms platform (using some default val -vv \ --broadcast -update-entity: ## update - forge script UpdateEntity \ - -f ${ETH_GOERLI_RPC_URL} \ - --chain-id 5 \ - --sender ${ownerAddress} \ - --mnemonic-paths ./nayms_mnemonic.txt \ - --mnemonic-indexes 19 \ - -vvvv \ - --broadcast - -update-commissions: ## update trading and premium commissions - forge script UpdateCommissions \ - -s "tradingAndPremium(address)" ${naymsDiamondAddress} \ - -f ${ETH_GOERLI_RPC_URL} \ - --chain-id 5 \ - --sender ${ownerAddress} \ - --mnemonic-paths ./nayms_mnemonic.txt \ - --mnemonic-indexes 19 \ - -vv \ - --broadcast - -docs: ## generate docs from natspec comments - yarn docgen - slither: ## run slither static analysis slither src/generated --config-file=slither.config.json --fail-none -verify-dry-run: ## dry run verify script, prints out commands to be executed - node cli-tools/verify.js --dry-run - -verify: ## verify contracts on chain (goerli) - node cli-tools/verify.js - -coderecon: ## code recon - @forge script CodeRecon \ - -s "run(string[] memory)" ${contractNames} \ - -f ${ETH_MAINNET_RPC_URL} \ - --chain-id 1 \ - --etherscan-api-key ${ETHERSCAN_API_KEY} \ - -vv \ - ; node cli-tools/parse-json.js - -compb: ## Compare bytecode - @forge script CheckBytecode \ - -s "run(uint8)" ${checkBytecodeAction} \ - -f ${ETH_MAINNET_RPC_URL} \ - --chain-id 1 \ - --etherscan-api-key ${ETHERSCAN_API_KEY} \ - --sender ${senderAddress} \ - --mnemonic-paths ./nayms_mnemonic.txt \ - --mnemonic-indexes 19 \ - -v \ - --ffi - -checkf: ## Check if facet exists in a diamond - @forge script DiamondChecker \ - -s "run(address, bytes4)" ${chkFacetAddress} ${selectorChk} \ - -f ${ETH_SEPOLIA_RPC_URL} \ - --chain-id 11155111 \ - --etherscan-api-key ${ETHERSCAN_API_KEY} \ - --sender ${ownerAddress} \ - --mnemonic-paths ./nayms_mnemonic.txt \ - --mnemonic-indexes 19 \ - -vv \ - --ffi - bn-mainnet: ## get block number for mainnet and replace FORK_BLOCK_1 in .env @result=$$(cast bn -r mainnet) && \ sed -i '' "s/^export FORK_BLOCK_1=.*/export FORK_BLOCK_1=$$result/" .env diff --git a/README.md b/README.md index d2ed237e..1ef9dc39 100644 --- a/README.md +++ b/README.md @@ -108,10 +108,7 @@ Following commands are provided for working with `anvil`, to make it more conven | Command | Description | | ----------- | ----------- | | `make anvil` | Run the local node seeding it with Nayms' shared wallet | -| `make anvil-debug` | Run Anvil in debug mode to get verbose log output | -| `make anvil-deploy` | Do a full deployment of Nayms' contracts to local node | -| `make anvil-upgrade` | Upgrade deployment of Nayms' contracts on local node | -| `make anvil-gtoken` | Deploy `GToken` to local node | +| `make anvil-docker` | Rund the local node inside a container, also seeding it with Nayms' shared wallet | | `make anvil-add-supported-external-token` | Add `GToken` as supported external token | | `make fork-sepolia`| Fork `Sepolia` test net locally | | `make fork-base-sepolia`| Fork `Base Sepolia` test net locally | diff --git a/cli-tools/contract.hbs b/cli-tools/contract.hbs deleted file mode 100644 index 35c494fb..00000000 --- a/cli-tools/contract.hbs +++ /dev/null @@ -1,83 +0,0 @@ - -{{{title}}} -{{{natspec.userdoc}}} - -{{#if ownVariables}} -## Globals - -> Note this contains internal vars as well due to a bug in the docgen procedure - -| Var | Type | -| --- | --- | -{{#ownVariables}} -{{#unless (eq visibility "internal")}} -| {{name}} | {{ type }} | -{{/unless}} -{{/ownVariables}} -{{/if}} - - -{{#if ownFunctions}} -## Functions - -{{#ownFunctions}} -### {{name}} -{{#if natspec.userdoc}}{{natspec.userdoc}}{{else}}No description{{/if}} -{{#if natspec.devdoc}}{{natspec.devdoc}}{{/if}} - -```solidity - function {{name}}( - {{#natspec.params}} - {{#lookup ../args.types @index}}{{/lookup}} {{param}}{{#if @last}}{{else}},{{/if}} - {{/natspec.params}} - ) {{visibility}}{{#astNode.modifiers}} {{modifierName.name}}{{/astNode.modifiers}}{{#if outputs}} returns ({{outputs}}){{/if}} -``` - -{{#if natspec.params}} -#### Arguments: -| Argument | Type | Description | -| --- | --- | --- | -{{#natspec.params}} -|`{{param}}` | {{#lookup ../args.types @index}}{{/lookup}} | {{description}} -{{~/natspec.params}} -| -

-{{/if}} - -{{#if natspec.returns}} -#### Returns: -| Type | Description | -| --- | --- | -{{#natspec.returns}} -|`{{param}}` | {{description}} -{{~/natspec.returns}} -| -

-{{/if}} - -{{/ownFunctions}} -{{/if}} - - -{{#if ownEvents}} -## Events - -{{#ownEvents}} -### {{name}} -{{#if natspec.userdoc}}{{natspec.userdoc}}{{else}}No description{{/if}} -{{#if natspec.devdoc}}> {{natspec.devdoc}}{{/if}} - - -{{#if natspec.params}} -#### Params: -| Param | Type | Indexed | Description | -| --- | --- | :---: | --- | -{{#natspec.params}} -|`{{param}}` | {{lookup ../args.types @index}} | {{#with (lookup ../astNode.parameters.parameters @index)}}{{#if indexed}}:white_check_mark:{{/if}}{{/with}} | {{description}} {{~-~}} -{{/natspec.params}} -| -

-{{/if}} - -{{/ownEvents}} -{{/if}} \ No newline at end of file diff --git a/cli-tools/contract_original.hbs b/cli-tools/contract_original.hbs deleted file mode 100644 index 94b10875..00000000 --- a/cli-tools/contract_original.hbs +++ /dev/null @@ -1,77 +0,0 @@ - -{{{title}}} -{{{natspec.userdoc}}} - -{{#if ownVariables}} -## Globals - -> Note this contains internal vars as well due to a bug in the docgen procedure - -| Var | Type | -| --- | --- | -{{#ownVariables}} -{{#unless (eq visibility "internal")}} -| {{name}} | {{ type }} | -{{/unless}} -{{/ownVariables}} -{{/if}} - - -{{#if ownFunctions}} -## Functions - -{{#ownFunctions}} -### {{name}} -{{#if natspec.userdoc}}{{natspec.userdoc}}{{else}}No description{{/if}} -{{#if natspec.devdoc}}{{natspec.devdoc}}{{/if}} - -```solidity - function {{name}}( - {{#natspec.params}} - {{#lookup ../args.types @index}}{{/lookup}} {{param}}{{#if @last}}{{else}},{{/if}} - {{/natspec.params}} - ) {{visibility}}{{#astNode.modifiers}} {{modifierName.name}}{{/astNode.modifiers}}{{#if outputs}} returns ({{outputs}}){{/if}} -``` - -{{#if natspec.params}} -#### Arguments: -| Argument | Type | Description | -| --- | --- | --- | -{{#natspec.params}} -|`{{param}}` | {{#lookup ../args.types @index}}{{/lookup}} | {{description}} -{{~/natspec.params}} -{{/if}} - -{{#if natspec.returns}} -#### Returns: -| Type | Description | -| --- | --- | -{{#natspec.returns}} -|`{{param}}` | {{description}} -{{~/natspec.returns}} -{{/if}} - -{{/ownFunctions}} -{{/if}} - - -{{#if ownEvents}} -## Events - -{{#ownEvents}} -### {{name}} -{{#if natspec.userdoc}}{{natspec.userdoc}}{{else}}No description{{/if}} -{{#if natspec.devdoc}}> {{natspec.devdoc}}{{/if}} - - -{{#if natspec.params}} -#### Params: -| Param | Type | Indexed | Description | -| --- | --- | :---: | --- | -{{#natspec.params}} -|`{{param}}` | {{lookup ../args.types @index}} | {{#with (lookup ../astNode.parameters.parameters @index)}}{{#if indexed}}:white_check_mark:{{/if}}{{/with}} | {{description}} {{~-~}} -{{/natspec.params}} -{{/if}} - -{{/ownEvents}} -{{/if}} \ No newline at end of file diff --git a/cli-tools/docgen.js b/cli-tools/docgen.js deleted file mode 100644 index ed960a18..00000000 --- a/cli-tools/docgen.js +++ /dev/null @@ -1,65 +0,0 @@ -const NODE_DIR = "./node_modules"; -const INPUT_DIR = "./src/diamonds/nayms/interfaces"; -const CONFIG_DIR = "./cli-tools"; -const OUTPUT_DIR = "./docs/facets"; -const README_FILE = "./docs/index.md"; -const SUMMARY_FILE = "./docs/summary.md"; -const EXCLUDES = "./src/utils"; - -const fs = require("fs"); -const path = require("path"); -const spawnSync = require("child_process").spawnSync; - -const excludeList = EXCLUDES.split(","); -const relativePath = path.relative(path.dirname(SUMMARY_FILE), OUTPUT_DIR); - -function lines(pathName) { - return fs.readFileSync(pathName, {encoding: "utf8"}).split("\r").join("").split("\n"); -} - -function scan(pathName, indentation) { - if (!excludeList.includes(pathName)) { - if (fs.lstatSync(pathName).isDirectory()) { - fs.appendFileSync(SUMMARY_FILE, indentation + "* " + path.basename(pathName) + "\n"); - for (const fileName of fs.readdirSync(pathName)) - scan(pathName + "/" + fileName, indentation + " "); - } - else if (pathName.endsWith("Facet.sol")) { - const text = path.basename(pathName).slice(0, -4); - const link = pathName.slice(INPUT_DIR.length, -4); - fs.appendFileSync(SUMMARY_FILE, indentation + "* [" + text + "](" + relativePath + link + ".md)\n"); - } - } -} - -function fix(pathName) { - if (fs.lstatSync(pathName).isDirectory()) { - for (const fileName of fs.readdirSync(pathName)) - fix(pathName + "/" + fileName); - } - else if (pathName.endsWith(".md")) { - fs.writeFileSync(pathName, lines(pathName).filter(line => line.trim().length > 0).join("\n") + "\n"); - } -} - -fs.writeFileSync (SUMMARY_FILE, "\n# Summary\n\n"); - -scan(INPUT_DIR, ""); - -const args = [ - NODE_DIR + "/solidity-docgen/dist/cli.js", - "--input=" + INPUT_DIR, - "--output=" + OUTPUT_DIR, - "--templates=" + CONFIG_DIR, - "--exclude=" + EXCLUDES, - "--solc-module=" + NODE_DIR + "/solc", - '--solc-settings=' + - JSON.stringify({ optimizer: { enabled: true, runs: 200 }, - }) -]; - -const result = spawnSync("node", args, {stdio: ["inherit", "inherit", "pipe"]}); -if (result.stderr.length > 0) - throw new Error(result.stderr); - -fix(OUTPUT_DIR); diff --git a/gemforge.config.cjs b/gemforge.config.cjs index f96c12d7..34cd7acf 100644 --- a/gemforge.config.cjs +++ b/gemforge.config.cjs @@ -1,12 +1,14 @@ require("dotenv").config(); const fs = require("fs"); -const ethers = require("ethers"); +const { ethers, utils } = require("ethers"); const MNEMONIC = fs.existsSync("./nayms_mnemonic.txt") ? fs.readFileSync("./nayms_mnemonic.txt").toString().trim() : "test test test test test test test test test test test junk"; const sysAdminAddress = ethers.Wallet.fromMnemonic(MNEMONIC)?.address; +const localSalt = utils.keccak256(utils.toUtf8Bytes("salty3")); + module.exports = { // Configuration file version version: 2, @@ -79,7 +81,7 @@ module.exports = { preBuild: "", postBuild: "", preDeploy: "", - postDeploy: "./script/gemforge/verify.js", + postDeploy: "", }, // Wallets to use for deployment wallets: { @@ -108,56 +110,64 @@ module.exports = { }, networks: { local: { rpcUrl: "http://localhost:8545" }, - sepolia: { rpcUrl: process.env.ETH_SEPOLIA_RPC_URL }, - mainnet: { rpcUrl: process.env.ETH_MAINNET_RPC_URL }, + sepolia: { + rpcUrl: process.env.ETH_SEPOLIA_RPC_URL, + contractVerification: { + foundry: { + apiUrl: "https://api-sepolia.etherscan.io/api", + apiKey: () => process.env.ETHERSCAN_API_KEY, + }, + }, + }, + mainnet: { + rpcUrl: process.env.ETH_MAINNET_RPC_URL, + contractVerification: { + foundry: { + apiUrl: "https://api.etherscan.io/api", + apiKey: () => process.env.ETHERSCAN_API_KEY, + }, + }, + }, baseSepolia: { rpcUrl: process.env.BASE_SEPOLIA_RPC_URL, - verifiers: [ - { - verifierName: "etherscan", - verifierUrl: "https://api-sepolia.basescan.org/api", - verifierApiKey: process.env.BASESCAN_API_KEY, - }, - { - verifierName: "blockscout", // needed for louper - verifierUrl: "https://base-sepolia.blockscout.com/api", - verifierApiKey: process.env.BLOCKSCOUT_API_KEY, + contractVerification: { + foundry: { + apiUrl: "https://api-sepolia.basescan.org/api", + apiKey: () => process.env.BASESCAN_API_KEY, }, - ], + }, }, base: { rpcUrl: process.env.BASE_MAINNET_RPC_URL, - verifiers: [ - { - verifierName: "etherscan", - verifierUrl: "https://api.basescan.org/api", - verifierApiKey: process.env.BASESCAN_API_KEY, + contractVerification: { + foundry: { + apiUrl: "https://api.basescan.org/api", + apiKey: () => process.env.BASESCAN_API_KEY, }, - ], + }, }, aurora: { rpcUrl: process.env.AURORA_MAINNET_RPC_URL, - verifiers: [ - { - verifierName: "aurora", - verifierUrl: "https://explorer.mainnet.aurora.dev/api", - verifierApiKey: process.env.BLOCKSCOUT_API_KEY, + contractVerification: { + foundry: { + apiUrl: "https://explorer.mainnet.aurora.dev/api", + apiKey: () => process.env.BLOCKSCOUT_API_KEY, }, - ], + }, }, auroraTestnet: { rpcUrl: process.env.AURORA_TESTNET_RPC_URL, - verifiers: [ - { - verifierName: "aurora", - verifierUrl: "https://explorer.testnet.aurora.dev/api", + contractVerification: { + foundry: { + apiUrl: "https://explorer.testnet.aurora.dev/api", + apiKey: () => process.env.BLOCKSCOUT_API_KEY, }, - ], + }, }, }, targets: { // `governance` attribute is only releveant for testnets, it's a wallet to use to auto approve the upgrade ID within the script - local: { network: "local", wallet: "devOwnerWallet", governance: "devSysAdminWallet", initArgs: [sysAdminAddress] }, + local: { network: "local", wallet: "devOwnerWallet", governance: "devSysAdminWallet", initArgs: [sysAdminAddress], create3Salt: localSalt }, sepolia: { network: "sepolia", wallet: "devOwnerWallet", governance: "devSysAdminWallet", initArgs: [sysAdminAddress] }, sepoliaFork: { network: "local", wallet: "devOwnerWallet", governance: "devSysAdminWallet", initArgs: [sysAdminAddress] }, mainnet: { network: "mainnet", wallet: "wallet3", initArgs: [sysAdminAddress] }, diff --git a/gemforge.deployments.json b/gemforge.deployments.json index c44ff442..cc0727ed 100644 --- a/gemforge.deployments.json +++ b/gemforge.deployments.json @@ -6,9 +6,9 @@ "name": "DiamondProxy", "fullyQualifiedName": "DiamondProxy.sol:DiamondProxy", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0x6d1ea0b274d7af0ca022b64f517f4f3be762995d87552d77a6f4ee2ff6978cbe", + "txHash": "0x1a2fa746d593988476cf71e5124a537683cbfc3bd171b5eaaf64c51e2c0cefec", "onChain": { - "address": "0x1e560E6adDF76b9335540565a96F4a93f371a56c", + "address": "0xE9A592C9d6f20eBe2eB302FAC5cd31EAF984Bb48", "constructorArgs": [ "0x931c3aC09202650148Edb2316e97815f904CF4fa" ] @@ -18,9 +18,9 @@ "name": "ACLFacet", "fullyQualifiedName": "ACLFacet.sol:ACLFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0xa46590e8de7d4ec32c712272b25feab6c651d8d01b10054d7c0f30c0e365adb5", + "txHash": "0x33f4ba195159855ccf9d35ed8ccd6c353e224a2716ce3da719e6abca9207427d", "onChain": { - "address": "0xeb91C729D4bD06F41C7624E6ef0a40a828479af2", + "address": "0xd6c79E894570d90739c44c2923F84C276567ABf8", "constructorArgs": [] } }, @@ -28,9 +28,9 @@ "name": "AdminFacet", "fullyQualifiedName": "AdminFacet.sol:AdminFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0x40f1d62e861f6017761819b20d72e3dd703001bcc9a77c526da50ed81417bd06", + "txHash": "0xb6534dbc917142f577e48909174708bf6c9cd65d70d721fc27318e689ead5c7a", "onChain": { - "address": "0xd6c79E894570d90739c44c2923F84C276567ABf8", + "address": "0x4F10acBA59A206a66713380De02F9c09880A822F", "constructorArgs": [] } }, @@ -38,9 +38,9 @@ "name": "EntityFacet", "fullyQualifiedName": "EntityFacet.sol:EntityFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0xb61c01169c230a60c43b87024d125b5112646728ff4777cf35f0b60a72d9fba7", + "txHash": "0xca3cae17c63189d49484cac876b94aa253e6635e31155fd5e881ac47d8df277c", "onChain": { - "address": "0x4F10acBA59A206a66713380De02F9c09880A822F", + "address": "0x175a1077Ccdb3bF380db95889610BF6877Fb104C", "constructorArgs": [] } }, @@ -48,9 +48,9 @@ "name": "GovernanceFacet", "fullyQualifiedName": "GovernanceFacet.sol:GovernanceFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0xff78b3f790000769bf5348c3c8566076c5721ee3e71a0ddb3d1290acab08d4f1", + "txHash": "0xf8fb9c2a40b7f6fa48100c29f423e056b5ca8bf80f8002529a222a58c20d1882", "onChain": { - "address": "0x175a1077Ccdb3bF380db95889610BF6877Fb104C", + "address": "0x76Ab1953794E1a7F522ADaf20112501dFc671b9f", "constructorArgs": [] } }, @@ -58,9 +58,9 @@ "name": "MarketFacet", "fullyQualifiedName": "MarketFacet.sol:MarketFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0xa987f01c5a7126ab2fcd8711ace03a4ef07f2e6c329849f8988d74a9ca1ca7bd", + "txHash": "0x195abb03cd273c35f5d337ad311c16d6cbd9b244cb9d4f39222d339cc78ecf4e", "onChain": { - "address": "0x76Ab1953794E1a7F522ADaf20112501dFc671b9f", + "address": "0xC0009342F733B8338C933505B3CE3F30285bb439", "constructorArgs": [] } }, @@ -68,9 +68,9 @@ "name": "NaymsOwnershipFacet", "fullyQualifiedName": "NaymsOwnershipFacet.sol:NaymsOwnershipFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0x497ced92dcf3a87bb44cca951f81c5d2cb8ba41d536697e20c72f2fdb1390db7", + "txHash": "0xff19b91167d3f5dc2a661fef32ef97ac71033a4f8737fd1e58ba4ee55ab63782", "onChain": { - "address": "0xC0009342F733B8338C933505B3CE3F30285bb439", + "address": "0xfADB5dB5d08BBf950256f79C3665CE8d71d8710f", "constructorArgs": [] } }, @@ -78,9 +78,9 @@ "name": "PhasedDiamondCutFacet", "fullyQualifiedName": "PhasedDiamondCutFacet.sol:PhasedDiamondCutFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0xb6f4c23ee3e42886b9332cc043d8e070983b8da0bfe8b06785b338189d6114b6", + "txHash": "0x9b71dc92a492001c31a41b861696b1bb26a60cd6e17b1dd1001309827d722b3c", "onChain": { - "address": "0xfADB5dB5d08BBf950256f79C3665CE8d71d8710f", + "address": "0x60EA14Bad701260fAE76F97639BC568F4e750b29", "constructorArgs": [] } }, @@ -88,9 +88,9 @@ "name": "SimplePolicyFacet", "fullyQualifiedName": "SimplePolicyFacet.sol:SimplePolicyFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0x4fe595cf6ad17e30da36fd71f51bb3767cf03e36f6b73e9eefffd65c161b6c02", + "txHash": "0x47c857cdeb477a4594d3e4b8a64cd21233dd4ba87a80dbcc393e45fb4cf5ac49", "onChain": { - "address": "0x60EA14Bad701260fAE76F97639BC568F4e750b29", + "address": "0x2F64b09a41b400a58D4485f08f6BCF14D944Ad6f", "constructorArgs": [] } }, @@ -98,9 +98,9 @@ "name": "StakingFacet", "fullyQualifiedName": "StakingFacet.sol:StakingFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0xf9e659777e5f136ada71305f75d4c746bcb33878d7d9d35e908ac039b171edff", + "txHash": "0xa077a8182a8c815af612621376515f8c496acaef01724aca1276d6bea8d57c05", "onChain": { - "address": "0x2F64b09a41b400a58D4485f08f6BCF14D944Ad6f", + "address": "0xE8737e94DcaA61B3354644D3a7177d91Abb7fBC0", "constructorArgs": [] } }, @@ -108,9 +108,9 @@ "name": "SystemFacet", "fullyQualifiedName": "SystemFacet.sol:SystemFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0x212d2448f9ef55c8163d46f678f2f83601c3be8b8bfe01d55285b74cbfdd11b4", + "txHash": "0xca22c45514b99d29222da5d2132cdc3028a1acd2e416acb1c3e50bca6d6326cb", "onChain": { - "address": "0xE8737e94DcaA61B3354644D3a7177d91Abb7fBC0", + "address": "0xC09f543dD405347105146BfD5b799233c69A7C70", "constructorArgs": [] } }, @@ -118,9 +118,9 @@ "name": "TokenizedVaultFacet", "fullyQualifiedName": "TokenizedVaultFacet.sol:TokenizedVaultFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0xd645b5b1325b244e2b07f287e422bd1d0b544b16ac93d2a0b50bd1b1bca2e8fb", + "txHash": "0xbfec3170742a3ea986cab5506bf9d7432fbbcc9b0784b9d23a727539164f6ee6", "onChain": { - "address": "0xC09f543dD405347105146BfD5b799233c69A7C70", + "address": "0x0c6815cEB188B0d877B08CF4B1F850Ed0F0929F0", "constructorArgs": [] } }, @@ -128,9 +128,9 @@ "name": "TokenizedVaultIOFacet", "fullyQualifiedName": "TokenizedVaultIOFacet.sol:TokenizedVaultIOFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0x93bdf006a5cd2300826c18e284de1e8d4122740abb8b9b93bb42a8c9bff09f6a", + "txHash": "0x0dc76eccacd37100e0b6e9a25239e96821158ff3871bbeb3af36ea8db169034d", "onChain": { - "address": "0x0c6815cEB188B0d877B08CF4B1F850Ed0F0929F0", + "address": "0xfc12A71BF96d541F439C79E3F16654e1c9B97935", "constructorArgs": [] } }, @@ -138,9 +138,9 @@ "name": "UserFacet", "fullyQualifiedName": "UserFacet.sol:UserFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0x61b62d4a18d8cfb5b6c1bf1d2dffaac85ce721305d984e01539b0dc3fde3c6b5", + "txHash": "0x9102d52e3272b61d0e75ab75dfd753ec1819191bd9a5779da711b43dfe2c73c7", "onChain": { - "address": "0xfc12A71BF96d541F439C79E3F16654e1c9B97935", + "address": "0x002d2970F2AacFD2344d2C1cc35b3985A374A73C", "constructorArgs": [] } }, @@ -148,9 +148,9 @@ "name": "InitDiamond", "fullyQualifiedName": "InitDiamond.sol:InitDiamond", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0xdcc6187f91f35071ba0252722041c056cec6b565221e4176692c9d60f1848d4d", + "txHash": "0xa2647f54fe69e069fcda71cdf4b9b10cb1c9deebb91de7bee1a2a9e0d9b97c93", "onChain": { - "address": "0x002d2970F2AacFD2344d2C1cc35b3985A374A73C", + "address": "0x23c9080C0A5236C6E6297fB1f4184C9f200a1A80", "constructorArgs": [] } } @@ -305,12 +305,12 @@ "chainId": 11155111, "contracts": [ { - "name": "PhasedDiamondCutFacet", - "fullyQualifiedName": "PhasedDiamondCutFacet.sol:PhasedDiamondCutFacet", + "name": "TokenizedVaultFacet", + "fullyQualifiedName": "TokenizedVaultFacet.sol:TokenizedVaultFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0xc48d3887b66dea8c4da120e22eb8c84f66ce73ae6558984d0a57eab1bfdbf66f", + "txHash": "0xd08283849d4f9c6a14cdfc047e2c7f590035d52beed286157946b042380ef973", "onChain": { - "address": "0x601211989B6B9EF4e792cb8c6e9a246703f5fc5B", + "address": "0xF3fF0876A4f4BD7f854085AF5638A66282a059B9", "constructorArgs": [] } }, @@ -318,9 +318,9 @@ "name": "ACLFacet", "fullyQualifiedName": "ACLFacet.sol:ACLFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0x8da2331eca87bc64bdfe512fec2269135a10f40769d6aee61564708953c0654b", + "txHash": "0x1c1db055eb1e6277dc155c3f336f87220b881404fd40cbd0a3b3ec903d4fb184", "onChain": { - "address": "0x3C1D30e508b0d919B1Af5bBb3c3813C4B261C2f3", + "address": "0x2B1973557a7b4409B2668d0F2157496aB3c0Af1C", "constructorArgs": [] } }, @@ -328,9 +328,9 @@ "name": "AdminFacet", "fullyQualifiedName": "AdminFacet.sol:AdminFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0x2336b4b0272ba66187daa614ce8e0f9a44db10e9f42ebd86ea7f399db963db1d", + "txHash": "0xb1d040b4fda20b36b1ec7497566e89c8eb30e4d1fc1797ca17dbae8a72e23c93", "onChain": { - "address": "0x33D269f18C833947FE498B68dB97bc8990feaaF7", + "address": "0x3eBF592D1c101D100383c41BC1174c7A6B328b5f", "constructorArgs": [] } }, @@ -338,9 +338,9 @@ "name": "EntityFacet", "fullyQualifiedName": "EntityFacet.sol:EntityFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0x6606d49f94a7a41937f199eff00c308d36dadeb7db85f72ca40fde520d647d39", + "txHash": "0x8f6ce633c11d231d71ac57e10038b0d019a36b0d3fffd91384708c02f57eb3ff", "onChain": { - "address": "0x48dF0CfAeF96B6B95B87A94b30536a099a44a1E4", + "address": "0xC5642620830C29dCC8ec7D5c922291D93643a08E", "constructorArgs": [] } }, @@ -348,9 +348,9 @@ "name": "GovernanceFacet", "fullyQualifiedName": "GovernanceFacet.sol:GovernanceFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0x53598cf0c82937e5fa35db4ad62dce9f261324dc00d428d0fae826a8fd0132d3", + "txHash": "0xfa63aa7472dfe142c10534624171669a3633b01d69c33562fd152af1a4f9102b", "onChain": { - "address": "0x19D0616218fA67f37E111f5deE950C67261565e7", + "address": "0x995929e8dc707F79a89D475740c3c185c187d081", "constructorArgs": [] } }, @@ -358,9 +358,9 @@ "name": "MarketFacet", "fullyQualifiedName": "MarketFacet.sol:MarketFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0x36acdc603d548b9273904920cab6708e7f7ccb349cfa7954246175faa453d0c3", + "txHash": "0xf5c9d3fbb66f2ab8fafbd6cb7054651dd29aebae2010b824cbf2184fa04296d4", "onChain": { - "address": "0x476e8E2491c717cd17f17c06329C481627Cf872A", + "address": "0x57987A12d0B69833b2a639603AE1B0B384fd5ebb", "constructorArgs": [] } }, @@ -368,9 +368,19 @@ "name": "NaymsOwnershipFacet", "fullyQualifiedName": "NaymsOwnershipFacet.sol:NaymsOwnershipFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0x974369ffe04773472ce0f3ad44656d0abf144574f5db27ae0a2375c3a630522d", + "txHash": "0xdc43a5d7a3bfcf38b4064e0b5a257f04d6603861b3e3481228b3a95d52fb58db", + "onChain": { + "address": "0x985816cD9fEf0A7134DA3B58D449a383d488dB52", + "constructorArgs": [] + } + }, + { + "name": "PhasedDiamondCutFacet", + "fullyQualifiedName": "PhasedDiamondCutFacet.sol:PhasedDiamondCutFacet", + "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", + "txHash": "0xfaff9fba8c897d6aa812e98370d6ba41b8b5084a3f05c128fa7f00f46d877d78", "onChain": { - "address": "0x814307E594990b0baFa158c198172FDabebC25C8", + "address": "0x06347Be0f259c5CDe48d9EE1Fd4A30EE7CD6cABF", "constructorArgs": [] } }, @@ -378,9 +388,9 @@ "name": "SimplePolicyFacet", "fullyQualifiedName": "SimplePolicyFacet.sol:SimplePolicyFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0x81cf52aba3e7c36501f55bb7324139b973bb05953942c43551692f3faf96beb7", + "txHash": "0xb7293ed86b413d98ae5a75ddc46ca43a99503febf8d1117079141af6d3b329b3", "onChain": { - "address": "0xd86c71e468Bc79f4070F47ee48BF93068e074C5d", + "address": "0x4d1cE83531B0d284D5d74095597E9FC2ab86609a", "constructorArgs": [] } }, @@ -388,9 +398,9 @@ "name": "StakingFacet", "fullyQualifiedName": "StakingFacet.sol:StakingFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0x4baf8c94c6f87405d43c8dd785d6b538e4bb83449d1f32c5ad69bcc437cc277e", + "txHash": "0x05f11bc3b013401aa817791482dc64980b719aa11fa92244173b3b74c2c7697c", "onChain": { - "address": "0xDdA3d3383171eb89b621F5a3679AabE9af6Ce410", + "address": "0xe1429C8fdBc8e684CFC6e239DA1bD93778D0C341", "constructorArgs": [] } }, @@ -398,39 +408,39 @@ "name": "SystemFacet", "fullyQualifiedName": "SystemFacet.sol:SystemFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0x271223ac639abf078d9efb73ade47ad29695c7533ce258f15b9cd6209f66e2f9", + "txHash": "0x35683bd1496eb9de25c562773af70678e4769c619fc72f21b09e02cbc87a745a", "onChain": { - "address": "0x8B84541a63bEf23AB3d2C5D99442DC898679cbf4", + "address": "0xa19BA1aEF33c5c13b6DF3793393784AA79C9A216", "constructorArgs": [] } }, { - "name": "TokenizedVaultFacet", - "fullyQualifiedName": "TokenizedVaultFacet.sol:TokenizedVaultFacet", + "name": "TokenizedVaultIOFacet", + "fullyQualifiedName": "TokenizedVaultIOFacet.sol:TokenizedVaultIOFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0x93cbec5279378fd0380605b4e25ab165c772dba0265507215176712be8486702", + "txHash": "0x0466e57a04d11617405ff411db604acd6145811f3f925b1dd0071da602fe3829", "onChain": { - "address": "0xf42E7193560134984B9678cCc9774EBCb0310e84", + "address": "0x08833B26056232899B8572038B126C5B1e38B39f", "constructorArgs": [] } }, { - "name": "TokenizedVaultIOFacet", - "fullyQualifiedName": "TokenizedVaultIOFacet.sol:TokenizedVaultIOFacet", + "name": "UserFacet", + "fullyQualifiedName": "UserFacet.sol:UserFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0x4beaf986786a0b607251f1449da89ea13f130e1356e60b7ec028a833c2b38f9e", + "txHash": "0x9749bac346509e35cab31014643a77d5bd9ef30c7a8832f706eaec97e8b1619d", "onChain": { - "address": "0x1f2323EccC25d4F7654C6bE579284D03AF6d9f34", + "address": "0x0B246415B03cA0A84Ae847A6395e3bd579aCA70d", "constructorArgs": [] } }, { - "name": "UserFacet", - "fullyQualifiedName": "UserFacet.sol:UserFacet", + "name": "ZapFacet", + "fullyQualifiedName": "ZapFacet.sol:ZapFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0xc23442d7cc74cad4dae5145de08c567876d6b9008ca89e15a2b7b5f8529c6568", + "txHash": "0x7dd40daf251907dda7a5cd0eb5ca9a05a3da1746776ea2a81e78a3795882b235", "onChain": { - "address": "0x1d82F910c67f8C0b330D36Df14BE9086aAAB849E", + "address": "0xb2928DA4C1F25EEa87ab1d1E288f774CBC193797", "constructorArgs": [] } }, @@ -1015,12 +1025,22 @@ "chainId": 84532, "contracts": [ { - "name": "StakingFacet", - "fullyQualifiedName": "StakingFacet.sol:StakingFacet", + "name": "TokenizedVaultFacet", + "fullyQualifiedName": "TokenizedVaultFacet.sol:TokenizedVaultFacet", + "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", + "txHash": "0x96e19e3578f39fd6ee33da76138bd8833429268a798030ecdc4f7292dd0bdfe7", + "onChain": { + "address": "0x88a46FD0c79B62aC708667Bc588403b70Ab685d5", + "constructorArgs": [] + } + }, + { + "name": "ACLFacet", + "fullyQualifiedName": "ACLFacet.sol:ACLFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0x3fb89ec05068933e313d491db53ecdc07f8a908f32a10d9a4d2d2cbd6aa99b8f", + "txHash": "0xaea385cf7df8d7b5797a4eb0f0515024ba8c1f48bd2138bca8bed4b5ceca0649", "onChain": { - "address": "0x56bA1BC0851DF8EF2d23212b71cF6aFf23de2B4f", + "address": "0xb9B266E32971b1fa28CAe98C4CE032ddc9b91860", "constructorArgs": [] } }, @@ -1028,9 +1048,9 @@ "name": "AdminFacet", "fullyQualifiedName": "AdminFacet.sol:AdminFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0x077b9ef69ea7a74560fbca0b91a475f7c673df741d25885c10bc426b1604a952", + "txHash": "0x11305277c71d310270f42cbfefa6b00adeefdb29064b0a60157aca4da085a11b", "onChain": { - "address": "0xD731bdF8b914ff662e8c6B237Fc506D2852795eE", + "address": "0xF42d09Eb8ce90134290487ad6501237d51996a9D", "constructorArgs": [] } }, @@ -1038,109 +1058,109 @@ "name": "EntityFacet", "fullyQualifiedName": "EntityFacet.sol:EntityFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0x3e027c275ddbd31d50aa211fb59d3003c0a844516479d526f75b99f765f4a660", + "txHash": "0x87a3f586b0e7230f92a10e878b025189b09826782064ef3dfb4b490627187a56", "onChain": { - "address": "0x9a410d7d81112396f99709a7fb82c7D32dF626A5", + "address": "0xddD63991dfB9D6Fe48229495e338Bdef75cbcd93", "constructorArgs": [] } }, { - "name": "MarketFacet", - "fullyQualifiedName": "MarketFacet.sol:MarketFacet", + "name": "GovernanceFacet", + "fullyQualifiedName": "GovernanceFacet.sol:GovernanceFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0x0d945cd636cf8bd884c6f859b20427d5ea0b6e3563ca72eeefcd5a1c006bbfcb", + "txHash": "0x7a3443e6fe25a6ff23b9cca0fa0f69f38845a784fd7fa1a4e64b1c3ca1b894cb", "onChain": { - "address": "0x66861b55273184c93d898a8adc38Dc49f9F97Ee2", + "address": "0xc17a2295Ffe12C55A49b247cA4a065187D0e7947", "constructorArgs": [] } }, { - "name": "PhasedDiamondCutFacet", - "fullyQualifiedName": "PhasedDiamondCutFacet.sol:PhasedDiamondCutFacet", + "name": "MarketFacet", + "fullyQualifiedName": "MarketFacet.sol:MarketFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0x741acd8976eb2f23fbc46f17d9425da7f0a2bc6992dbfd2ebb63939257f56bfe", + "txHash": "0x54230ce83328f621e4650b55097e05d4045168984c5d68e1453da0494d84f83a", "onChain": { - "address": "0xB5d8896CEda82FA3523b00cF37e91D1F26e80E0E", + "address": "0xFdFeF31eEeB751E23430e668dc32eE76797A0633", "constructorArgs": [] } }, { - "name": "SimplePolicyFacet", - "fullyQualifiedName": "SimplePolicyFacet.sol:SimplePolicyFacet", + "name": "NaymsOwnershipFacet", + "fullyQualifiedName": "NaymsOwnershipFacet.sol:NaymsOwnershipFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0x3ec3b0cf2182fabbb8425442ebb4b4e328dd709d519f0af2f9ef364eaa641a79", + "txHash": "0xdd55e10c600fd3034ec79b11b69b7ff48996f9969769fecc84cdf3fc60af2b53", "onChain": { - "address": "0xFAf0C7120dcd00a4235261328869934ccA6ef2d3", + "address": "0xf547FE61ae0F09991B0cdc67045aA3af8C416dD3", "constructorArgs": [] } }, { - "name": "SystemFacet", - "fullyQualifiedName": "SystemFacet.sol:SystemFacet", + "name": "SimplePolicyFacet", + "fullyQualifiedName": "SimplePolicyFacet.sol:SimplePolicyFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0xf94b8b06b7f0ba633b82b8ec1db2e0e1986b9bf97ad734c9a513bd6148d29ade", + "txHash": "0x2156d75ffdaf67cde1887b7d37245372d007d8700d2058bfe28896782b9020ce", "onChain": { - "address": "0x828210FD60505415FcA766FDE661D696CD6Af02D", + "address": "0xB6ddECA657aD25D9739CE61F95BB69FD132a2fa6", "constructorArgs": [] } }, { - "name": "TokenizedVaultFacet", - "fullyQualifiedName": "TokenizedVaultFacet.sol:TokenizedVaultFacet", + "name": "StakingFacet", + "fullyQualifiedName": "StakingFacet.sol:StakingFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0x31bdb1f258f71d8e8e0fe7697c52a235920b6ed2038fb1798bba3f2c0004082b", + "txHash": "0x36360314067dea521d635b4a3c021057f5d659c2b3efe3fba33535a627c9b964", "onChain": { - "address": "0xc89B93249738A582Ad92D0faBa94C7BCE04a7a8F", + "address": "0xA8E58224a3f440555c9338893dAeA9186F58db5a", "constructorArgs": [] } }, { - "name": "TokenizedVaultIOFacet", - "fullyQualifiedName": "TokenizedVaultIOFacet.sol:TokenizedVaultIOFacet", + "name": "SystemFacet", + "fullyQualifiedName": "SystemFacet.sol:SystemFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0xc5be695eb3c99ca7cf1ad844a9bb1a2f815d6aedb4e66747d7c89b874032d421", + "txHash": "0x231a4784a5711337e3e82713f819b3e52573d5983fe5cf388d9ccc8d205a01d0", "onChain": { - "address": "0x6EA81F51B6C12cE8b029249c872f0583F099bcDb", + "address": "0x1d51A0b14729B188E818eD5A435BEeDe7bA71ACe", "constructorArgs": [] } }, { - "name": "ACLFacet", - "fullyQualifiedName": "ACLFacet.sol:ACLFacet", + "name": "TokenizedVaultIOFacet", + "fullyQualifiedName": "TokenizedVaultIOFacet.sol:TokenizedVaultIOFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0x046a509d90cfbdcd4ebc3ecf603c2e0ebb83c09457f17c9e81c9fecc92ef4639", + "txHash": "0xadb9ed81fa5ed8293ff6f97f66d2a3a15fb20c73cf864bfc04140db7a7ddb4ce", "onChain": { - "address": "0x634095e86FE2E9Ad506D5daebE0b1E22E71252d4", + "address": "0xd869BE8195D193101d9bd4711049C1047F03a8c8", "constructorArgs": [] } }, { - "name": "GovernanceFacet", - "fullyQualifiedName": "GovernanceFacet.sol:GovernanceFacet", + "name": "UserFacet", + "fullyQualifiedName": "UserFacet.sol:UserFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0x802d89ff3c619bb6d5d1b1f3afadb723e28adfcbbe61ad6401303938feabddd5", + "txHash": "0x668ced3d0c12c15a40363d0fff119c9c1ac2dddf00ca5982f30ae492035bff19", "onChain": { - "address": "0x3286dA55EaeB7Fe68bCDF901fA9685a4CaE264dF", + "address": "0x31907a5D8a8BB90ef8203884E9d76774Fa5ad3a8", "constructorArgs": [] } }, { - "name": "NaymsOwnershipFacet", - "fullyQualifiedName": "NaymsOwnershipFacet.sol:NaymsOwnershipFacet", + "name": "ZapFacet", + "fullyQualifiedName": "ZapFacet.sol:ZapFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0xd06d7131c3897714df6bd45b7dd57618f268b86eb9c2259f8532f45cd9626c36", + "txHash": "0x32d70cd5723cf1fe4141cb3e0cd7f23d702e19016684eb3bf7195796a666af03", "onChain": { - "address": "0xebeF106aBf7f32fa64Aa9d6C96766A6534CB83d3", + "address": "0xCb7d275474B2336Ac9DBDE0a0669a7dfCf299D3e", "constructorArgs": [] } }, { - "name": "UserFacet", - "fullyQualifiedName": "UserFacet.sol:UserFacet", + "name": "PhasedDiamondCutFacet", + "fullyQualifiedName": "PhasedDiamondCutFacet.sol:PhasedDiamondCutFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0x42215092959b93afbbb476f79aab950389b01b2c926f2d62c0b98daf5f490744", + "txHash": "0x741acd8976eb2f23fbc46f17d9425da7f0a2bc6992dbfd2ebb63939257f56bfe", "onChain": { - "address": "0x860C9188bcc6b794BA7EDD06aA5159c6b74bAFeB", + "address": "0xB5d8896CEda82FA3523b00cF37e91D1F26e80E0E", "constructorArgs": [] } }, @@ -1189,22 +1209,32 @@ "chainId": 1313161555, "contracts": [ { - "name": "PhasedDiamondCutFacet", - "fullyQualifiedName": "PhasedDiamondCutFacet.sol:PhasedDiamondCutFacet", + "name": "TokenizedVaultFacet", + "fullyQualifiedName": "TokenizedVaultFacet.sol:TokenizedVaultFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0x543a981cd09664ce2430f080f393d7f84f972fba1d828f980a2ec40942bc4096", + "txHash": "0xa8f3c326f453eb941477a827d22c87e9cb0ada4a35dcfd65253b6e3f3d0bd1ec", "onChain": { - "address": "0x86952dC734702030Eaaede33E070159F7A8B07dc", + "address": "0x21702829450ffb9bdBC839063252DEbb87a832d9", "constructorArgs": [] } }, { - "name": "StakingFacet", - "fullyQualifiedName": "StakingFacet.sol:StakingFacet", + "name": "ACLFacet", + "fullyQualifiedName": "ACLFacet.sol:ACLFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0x61dbca53748b3a729b67160690edec962cbaaeada9795574e4115e103e695b69", + "txHash": "0xa425708122492fd66c40f161e6e4dee09187edf733d614fa23ad1545072da4ff", "onChain": { - "address": "0x894e7a37f08992C457E522a5610eFc1714935E26", + "address": "0x8A38B14aE079581eBaCfC391bBb3D49057953BF4", + "constructorArgs": [] + } + }, + { + "name": "AdminFacet", + "fullyQualifiedName": "AdminFacet.sol:AdminFacet", + "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", + "txHash": "0xbbeb4a99f2b5bcb57ab341d6cd8f47fdffeeb879e496b038b05acf4db665cba5", + "onChain": { + "address": "0x268593c8f62F0D77E86D19697Ee9c9A2063B15A5", "constructorArgs": [] } }, @@ -1212,19 +1242,19 @@ "name": "EntityFacet", "fullyQualifiedName": "EntityFacet.sol:EntityFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0x8fb4777c91342976ecc41ef431e4add163dfb1bc01526e67e6f6b9d250fc0759", + "txHash": "0x4645a4096e998eb1f91c279701efdba871635538deb7eb73c071ebeeec298073", "onChain": { - "address": "0x62AE01F93C7280271616E9Dd3A3789Fc734F8192", + "address": "0x1AE9B5974fD8649AE58Fc1FF509fa96892FCb5E8", "constructorArgs": [] } }, { - "name": "ACLFacet", - "fullyQualifiedName": "ACLFacet.sol:ACLFacet", + "name": "GovernanceFacet", + "fullyQualifiedName": "GovernanceFacet.sol:GovernanceFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0x10d09e97903a93fe989014b3751b78e3ee00c456827365d71957bdd729183e63", + "txHash": "0x0537a33a6703375672d01e1429a15b66bd296835cf74ef23a9745becb4c1ff91", "onChain": { - "address": "0x24da9216FD050b124Ab77FC5072E9377566Dc598", + "address": "0xB71f83ECC8dDf00101626d3FF95f6ee0Df65eaDc", "constructorArgs": [] } }, @@ -1232,102 +1262,102 @@ "name": "MarketFacet", "fullyQualifiedName": "MarketFacet.sol:MarketFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0x6cd6f28aa13c1038113ecebf064536db3cfd2ee000a8ead09b307c2dc70f3525", + "txHash": "0x297722357cbb3a819dad9168ae246a7a98c4208ba6eeec180554c25ed7fd65d6", "onChain": { - "address": "0x1c0e6919d498F3f49f8b7Efdda23C5F7AA56416E", + "address": "0x8637F569f6Aeac33EdDBB8DBA97407Ef619AeC37", "constructorArgs": [] } }, { - "name": "SimplePolicyFacet", - "fullyQualifiedName": "SimplePolicyFacet.sol:SimplePolicyFacet", + "name": "NaymsOwnershipFacet", + "fullyQualifiedName": "NaymsOwnershipFacet.sol:NaymsOwnershipFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0xea5ae74755072167258c1ecd1ce079c31292ffb1b47e01a1886146f778d12096", + "txHash": "0x8f0be8bd64149baa4d9e5ab740f9239d15bc81c8cd805eb08d18bb840afa7925", "onChain": { - "address": "0xc92124E373b331AA22f8f277651b903aA8e96583", + "address": "0x783BAA0E392F9161B47cb8Edd7Ed5C6351D1A39c", "constructorArgs": [] } }, { - "name": "SystemFacet", - "fullyQualifiedName": "SystemFacet.sol:SystemFacet", + "name": "PhasedDiamondCutFacet", + "fullyQualifiedName": "PhasedDiamondCutFacet.sol:PhasedDiamondCutFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0x762d341ab93a7cff5e28d8ec0f0c59b0ac2262bba5c39659ec7e2ff20e391c57", + "txHash": "0xacbc058fb062f6b138d414d5cc2ee3c3904ceaf50509b32fd4dce3a07fc8255f", "onChain": { - "address": "0xfcA80872073Db2dE6726b087537D7005C235fCeE", + "address": "0x5BaE89a53F7d81476c3A472bb9FD97701de118C4", "constructorArgs": [] } }, { - "name": "AdminFacet", - "fullyQualifiedName": "AdminFacet.sol:AdminFacet", + "name": "SimplePolicyFacet", + "fullyQualifiedName": "SimplePolicyFacet.sol:SimplePolicyFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0x37ca6c1919aab114c3ecc1f328f061eb9241725ee9354dceaf7cc84418c4edeb", + "txHash": "0x71d53672b4b0e3706dc926c08cf31c2dfe2040bfc7d79471e4822dd3d4beea98", "onChain": { - "address": "0x06bEc7aAC7cC3Dd6b1B8a3A01856d31127487861", + "address": "0xa35cC6271D970f52F80e9325aB74b19C0b823F66", "constructorArgs": [] } }, { - "name": "DiamondProxy", - "fullyQualifiedName": "DiamondProxy.sol:DiamondProxy", + "name": "StakingFacet", + "fullyQualifiedName": "StakingFacet.sol:StakingFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0x094735b16130b5e29d9f454e65edf86c6970930b18610a90de1c3c2990ffd5ee", + "txHash": "0xc0ee96a7c496d6ef067bcc8f5d0811272a7b62a85575429fc178651093c7bd04", "onChain": { - "address": "0x4F10acBA59A206a66713380De02F9c09880A822F", - "constructorArgs": [ - "0x931c3aC09202650148Edb2316e97815f904CF4fa" - ] + "address": "0x8F149BF305bb363e7Bd1f46116360c37821dd903", + "constructorArgs": [] } }, { - "name": "GovernanceFacet", - "fullyQualifiedName": "GovernanceFacet.sol:GovernanceFacet", + "name": "SystemFacet", + "fullyQualifiedName": "SystemFacet.sol:SystemFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0xb7dde44e8270ee979433ef029a3d3251b9ec45e98db76179cd0978daa27ae6ea", + "txHash": "0xd55aee4737b019e21838d8883f14401456f495f7efae818bc0c3f51b250c5e49", "onChain": { - "address": "0xfADB5dB5d08BBf950256f79C3665CE8d71d8710f", + "address": "0x6CeAa2E66E4b068f75D955bA18f9A0f72e5d532C", "constructorArgs": [] } }, { - "name": "NaymsOwnershipFacet", - "fullyQualifiedName": "NaymsOwnershipFacet.sol:NaymsOwnershipFacet", + "name": "TokenizedVaultIOFacet", + "fullyQualifiedName": "TokenizedVaultIOFacet.sol:TokenizedVaultIOFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0x850219af7cb60b2f1d6a004909c74ed0e08e6e287cde81f3c288aeb54b189020", + "txHash": "0x1d23d86e9eaef991952bdad3cfaefe9e68610419e084658163b67bf382aedcc8", "onChain": { - "address": "0x2F64b09a41b400a58D4485f08f6BCF14D944Ad6f", + "address": "0xc8c80Db133edD26554F73648a04af70F5241dBf4", "constructorArgs": [] } }, { - "name": "TokenizedVaultFacet", - "fullyQualifiedName": "TokenizedVaultFacet.sol:TokenizedVaultFacet", + "name": "UserFacet", + "fullyQualifiedName": "UserFacet.sol:UserFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0xa63377a28b7a6d0e20d089bbbbfc8b860df9e4781e490ad72e94517a08f52772", + "txHash": "0x14e0733e38ec3f72d338a179f6d202585f324fa875547b0a2ee659de894d9d6d", "onChain": { - "address": "0x002d2970F2AacFD2344d2C1cc35b3985A374A73C", + "address": "0x7FeF514DE762015229B51080c75d1C65016f38b4", "constructorArgs": [] } }, { - "name": "TokenizedVaultIOFacet", - "fullyQualifiedName": "TokenizedVaultIOFacet.sol:TokenizedVaultIOFacet", + "name": "ZapFacet", + "fullyQualifiedName": "ZapFacet.sol:ZapFacet", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0xb7d4d93ef9274cf2584c048a971ba00a2bf6286fc6ade61b33187b090ce23051", + "txHash": "0x9c838a364cc350f1af090822fd79d0948499a6724ef525a3b71e6df334589de0", "onChain": { - "address": "0x23c9080C0A5236C6E6297fB1f4184C9f200a1A80", + "address": "0x3d28d86602469CA216d05937D098C610e37EE25b", "constructorArgs": [] } }, { - "name": "UserFacet", - "fullyQualifiedName": "UserFacet.sol:UserFacet", + "name": "DiamondProxy", + "fullyQualifiedName": "DiamondProxy.sol:DiamondProxy", "sender": "0x931c3aC09202650148Edb2316e97815f904CF4fa", - "txHash": "0x6160fb1fb7657cde4d9c42e6b4853bc7f4b9eee248c0aa2df1e926079b8cc194", + "txHash": "0x094735b16130b5e29d9f454e65edf86c6970930b18610a90de1c3c2990ffd5ee", "onChain": { - "address": "0x909677eBF6e09B669dBe01950E9F3FfCe7602097", - "constructorArgs": [] + "address": "0x4F10acBA59A206a66713380De02F9c09880A822F", + "constructorArgs": [ + "0x931c3aC09202650148Edb2316e97815f904CF4fa" + ] } }, { diff --git a/lib/solidity-lib b/lib/solidity-lib deleted file mode 160000 index c01640b0..00000000 --- a/lib/solidity-lib +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c01640b0f0f1d8a85cba8de378cc48469fcfd9a6 diff --git a/lib/solidity-stringutils b/lib/solidity-stringutils deleted file mode 160000 index 4b2fcc43..00000000 --- a/lib/solidity-stringutils +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4b2fcc43fa0426e19ce88b1f1ec16f5903a2e461 diff --git a/package.json b/package.json index 30053bd5..6294300f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@nayms/contracts", - "version": "3.9.2", + "version": "3.9.7", "main": "index.js", "repository": "https://github.com/nayms/contracts-v3.git", "author": "Kevin Park ", @@ -30,12 +30,12 @@ "solhint:check": "solhint --config ./.solhint.json 'src/**/*.sol'", "lint": "yarn prettier && yarn run solhint", "lint:check": "yarn prettier:check && yarn run solhint:check", - "docgen": "rm -rf docs/facets/*Facet.md && node cli-tools/docgen.js", "anvil": "anvil --host 0.0.0.0 --chain-id 31337 --accounts 30 -m ./nayms_mnemonic.txt", "build": "gemforge build", "deploy": "./script/gemforge/deploy.js", "query": "gemforge query", - "test": "forge test --no-match-test testReplaceDiamondCut" + "verify": "gemforge verify", + "test": "forge test --no-match-test testFork" }, "devDependencies": { "chalk": "4", diff --git a/remappings.txt b/remappings.txt index c72a2f36..e8608106 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,10 +1,7 @@ forge-std/=lib/forge-std/src/ +ds-test/=lib/ds-test/src/ @openzeppelin/contracts/=lib/oz/contracts/ diamond-2-hardhat/=lib/diamond-2-hardhat/contracts/ -ds-test/=lib/ds-test/src/ erc4626-tests/=lib/oz/lib/erc4626-tests/ -oz/=lib/oz/ solady/=lib/solady/src/ -solidity-lib/=lib/solidity-lib/contracts/ -solidity-stringutils/=lib/solidity-stringutils/ solmate/=lib/solmate/src/ diff --git a/script/gemforge/verify.js b/script/gemforge/verify.js deleted file mode 100755 index 55b92ffe..00000000 --- a/script/gemforge/verify.js +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env node - -const chalk = require("chalk"); - -(async () => { - require("dotenv").config(); - const { $ } = await import("execa"); - - const deploymentInfo = require("../../gemforge.deployments.json"); - const gemforgeConfig = require("../../gemforge.config.cjs"); - - const target = process.env.GEMFORGE_DEPLOY_TARGET; - if (!target) { - throw new Error("GEMFORGE_DEPLOY_TARGET env var not set"); - } - - // skip for localhost and forks - if (target === "local" || /fork/i.test(target)) { - console.log("Skipping contract verification on", target); - return; - } - - const contracts = deploymentInfo[target]?.contracts || []; - const verifiers = gemforgeConfig.networks?.[target]?.verifiers || [{ verifierName: "etherscan" }]; - - for (const { verifierName, verifierUrl, verifierApiKey } of verifiers) { - console.log(chalk.cyan(`Verifying ${target} target on ${verifierName}`)); - - const apiKey = verifierApiKey || process.env.ETHERSCAN_API_KEY; - const verificationArg = verifierUrl ? `--verifier-url=${verifierUrl}` : `--verifier=${verifierName}`; - - for (const { name, onChain } of contracts) { - let args = "0x"; - - if (onChain.constructorArgs.length) { - args = (await $`cast abi-encode constructor(address) ${onChain.constructorArgs.join(" ")}`).stdout; - } - - console.log(`Verifying ${name} at ${onChain.address} with args ${args}`); - - await $`forge v ${onChain.address} ${name} --constructor-args ${args} --chain-id ${deploymentInfo[target].chainId} ${verificationArg} --etherscan-api-key ${apiKey} --watch`; - - console.log(chalk.green(` Verified!`)); - } - } -})(); diff --git a/src/facets/AdminFacet.sol b/src/facets/AdminFacet.sol index 6cea920b..43682575 100644 --- a/src/facets/AdminFacet.sol +++ b/src/facets/AdminFacet.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.20; import { AppStorage, LibAppStorage } from "../shared/AppStorage.sol"; +import { OnboardingApproval } from "../shared/FreeStructs.sol"; import { Modifiers } from "../shared/Modifiers.sol"; import { LibAdmin } from "../libs/LibAdmin.sol"; import { LibObject } from "../libs/LibObject.sol"; @@ -148,25 +149,21 @@ contract AdminFacet is Modifiers { } /** - * @notice Approve a user address for self-onboarding - * @param _userAddress user account address + * @notice Create a token holder entity for a user account + * @param _onboardingApproval onboarding approval parameters, includes user address, entity ID and role ID */ - function approveSelfOnboarding(address _userAddress, bytes32 _entityId, bytes32 _roleId) external assertPrivilege(LibAdmin._getSystemId(), LC.GROUP_ONBOARDING_APPROVERS) { - LibAdmin._approveSelfOnboarding(_userAddress, _entityId, _roleId); + function onboardViaSignature(OnboardingApproval calldata _onboardingApproval) external { + LibAdmin._onboardUserViaSignature(_onboardingApproval); } /** - * @notice Create a token holder entity for a user account + * @notice Hash to be signed by the onboarding approver + * @param _userAddress Address being approved to onboard + * @param _entityId Entity ID being approved for onboarding + * @param _roleId Role being apprved for onboarding + * @return Onboarding approval digest */ - function onboard() external { - LibAdmin._onboardUser(msg.sender); - } - - function isSelfOnboardingApproved(address _userAddress, bytes32 _entityId, bytes32 _roleId) external view returns (bool) { - return LibAdmin._isSelfOnboardingApproved(_userAddress, _entityId, _roleId); - } - - function cancelSelfOnboarding(address _user) external assertPrivilege(LibAdmin._getSystemId(), LC.GROUP_SYSTEM_MANAGERS) { - LibAdmin._cancelSelfOnboarding(_user); + function getOnboardingHash(address _userAddress, bytes32 _entityId, bytes32 _roleId) external view returns (bytes32) { + return LibAdmin._getOnboardingHash(_userAddress, _entityId, _roleId); } } diff --git a/src/facets/StakingFacet.sol b/src/facets/StakingFacet.sol index 5dce2f11..af94e7b0 100644 --- a/src/facets/StakingFacet.sol +++ b/src/facets/StakingFacet.sol @@ -134,6 +134,13 @@ contract StakingFacet is Modifiers { LibTokenizedVaultStaking._collectRewards(parentId, _entityId, lastPaid); } + function compoundRewards(bytes32 _entityId) external notLocked { + bytes32 parentId = LibObject._getParent(msg.sender._getIdForAddress()); + uint64 lastPaid = LibTokenizedVaultStaking._lastPaidInterval(_entityId); + + LibTokenizedVaultStaking._compoundRewards(parentId, _entityId, lastPaid); + } + /** * @notice Collect rewards for a staker * @param _entityId staking entity ID diff --git a/src/facets/TokenizedVaultFacet.sol b/src/facets/TokenizedVaultFacet.sol index 87a5693b..63dcd6ba 100644 --- a/src/facets/TokenizedVaultFacet.sol +++ b/src/facets/TokenizedVaultFacet.sol @@ -140,6 +140,16 @@ contract TokenizedVaultFacet is Modifiers, ReentrancyGuard { amount = LibTokenizedVault._getLockedBalance(_entityId, _tokenId); } + /** + * @notice Get the amount of tokens that an entity has available (deposited, but not locked) + * @param _entityId Unique platform ID of the entity. + * @param _tokenId The ID assigned to an external token. + * @return amount of tokens that the entity has available + */ + function getAvailableBalance(bytes32 _entityId, bytes32 _tokenId) external view returns (uint256) { + return LibTokenizedVault._getAvailableBalance(_entityId, _tokenId); + } + /** * @notice A system admin can transfer funds from an ID to another one. * diff --git a/src/facets/TokenizedVaultIOFacet.sol b/src/facets/TokenizedVaultIOFacet.sol index 2306116a..b83d0165 100644 --- a/src/facets/TokenizedVaultIOFacet.sol +++ b/src/facets/TokenizedVaultIOFacet.sol @@ -9,7 +9,7 @@ import { LibObject } from "../libs/LibObject.sol"; import { LibConstants as LC } from "../libs/LibConstants.sol"; import { LibACL } from "../libs/LibACL.sol"; import { LibHelpers } from "../libs/LibHelpers.sol"; -import { ExternalWithdrawInvalidReceiver } from "../shared/CustomErrors.sol"; +import { ExternalWithdrawInvalidReceiver, InvalidERC20Token } from "../shared/CustomErrors.sol"; import { ReentrancyGuard } from "../utils/ReentrancyGuard.sol"; /** @@ -29,8 +29,10 @@ contract TokenizedVaultIOFacet is Modifiers, ReentrancyGuard { address _externalTokenAddress, uint256 _amount ) external notLocked nonReentrant assertPrivilege(LibObject._getParentFromAddress(msg.sender), LC.GROUP_EXTERNAL_DEPOSIT) { - // a user can only deposit an approved external ERC20 token - require(LibAdmin._isSupportedExternalTokenAddress(_externalTokenAddress), "extDeposit: invalid ERC20 token"); + if (!LibAdmin._isSupportedExternalTokenAddress(_externalTokenAddress)) { + revert InvalidERC20Token(_externalTokenAddress, "extDeposit"); + } + // a user can only deposit to their valid entity bytes32 entityId = LibObject._getParentFromAddress(msg.sender); require(LibEntity._isEntity(entityId), "extDeposit: invalid receiver"); diff --git a/src/facets/ZapFacet.sol b/src/facets/ZapFacet.sol new file mode 100644 index 00000000..3a422759 --- /dev/null +++ b/src/facets/ZapFacet.sol @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +import { PermitSignature, OnboardingApproval } from "../shared/FreeStructs.sol"; +import { InvalidERC20Token } from "../shared/CustomErrors.sol"; +import { Modifiers } from "../shared/Modifiers.sol"; +import { LibTokenizedVaultIO } from "../libs/LibTokenizedVaultIO.sol"; +import { LibACL } from "../libs/LibACL.sol"; +import { LibAdmin } from "../libs/LibAdmin.sol"; +import { LibObject } from "../libs/LibObject.sol"; +import { LibHelpers } from "../libs/LibHelpers.sol"; +import { LibConstants as LC } from "../libs/LibConstants.sol"; +import { ReentrancyGuard } from "../utils/ReentrancyGuard.sol"; +import { LibTokenizedVaultStaking } from "../libs/LibTokenizedVaultStaking.sol"; +import { IERC20 } from "../interfaces/IERC20.sol"; +import { LibMarket } from "../libs/LibMarket.sol"; + +contract ZapFacet is Modifiers, ReentrancyGuard { + /** + * @notice Deposit and stake funds into msg.sender's Nayms platform entity in one transaction using permit + * @dev Uses permit to approve token transfer, deposits from msg.sender to their associated entity, and stakes the amount + * @param _externalTokenAddress Token address + * @param _stakingEntityId Staking entity ID + * @param _amountToDeposit Deposit amount + * @param _amountToStake Stake amount + * @param _permitSignature The permit signature parameters + * @param _onboardingApproval The onboarding approval parameters + */ + function zapStake( + address _externalTokenAddress, + bytes32 _stakingEntityId, + uint256 _amountToDeposit, + uint256 _amountToStake, + PermitSignature calldata _permitSignature, + OnboardingApproval calldata _onboardingApproval + ) external notLocked nonReentrant { + if (!LibAdmin._isSupportedExternalTokenAddress(_externalTokenAddress)) { + revert InvalidERC20Token(_externalTokenAddress, "zapStake"); + } + + bytes32 parentId = LibObject._getParentFromAddress(msg.sender); + + if (_onboardingApproval.entityId != 0 && parentId != _onboardingApproval.entityId) { + LibAdmin._onboardUserViaSignature(_onboardingApproval); + } + + // Use permit to set allowance + IERC20(_externalTokenAddress).permit(msg.sender, address(this), _amountToDeposit, _permitSignature.deadline, _permitSignature.v, _permitSignature.r, _permitSignature.s); + + // Perform the deposit + LibTokenizedVaultIO._externalDeposit(parentId, _externalTokenAddress, _amountToDeposit); + + // Stake the deposited amount + LibTokenizedVaultStaking._stake(parentId, _stakingEntityId, _amountToStake); + } + + /** + * @notice Deposit tokens and execute a limit order in one transaction using permit + * @dev Uses permit to approve token transfer and performs external deposit and limit order execution + * @param _externalTokenAddress Token address + * @param _depositAmount Amount to deposit + * @param _sellToken Sell token ID + * @param _sellAmount Sell amount + * @param _buyToken Buy token ID + * @param _buyAmount Buy amount + * @param _permitSignature The permit signature parameters + * @param _onboardingApproval The onboarding approval parameters + */ + function zapOrder( + address _externalTokenAddress, + uint256 _depositAmount, + bytes32 _sellToken, + uint256 _sellAmount, + bytes32 _buyToken, + uint256 _buyAmount, + PermitSignature calldata _permitSignature, + OnboardingApproval calldata _onboardingApproval + ) external notLocked nonReentrant { + if (!LibAdmin._isSupportedExternalTokenAddress(_externalTokenAddress)) { + revert InvalidERC20Token(_externalTokenAddress, "zapOrder"); + } + + bytes32 parentId = _onboardingApproval.entityId; + + bool isOnboardingCP = _onboardingApproval.roleId == LibHelpers._stringToBytes32(LC.ROLE_ENTITY_CP); + bool isCurrentlyCP = LibACL._isInGroup(parentId, LibHelpers._stringToBytes32(LC.SYSTEM_IDENTIFIER), LibHelpers._stringToBytes32(LC.GROUP_CAPITAL_PROVIDERS)); + + if (!isCurrentlyCP && isOnboardingCP) { + LibAdmin._onboardUserViaSignature(_onboardingApproval); + } + + LibACL._assertPriviledge(parentId, LC.GROUP_EXECUTE_LIMIT_OFFER); + + // Use permit to set allowance + IERC20(_externalTokenAddress).permit(msg.sender, address(this), _depositAmount, _permitSignature.deadline, _permitSignature.v, _permitSignature.r, _permitSignature.s); + + // Perform the external deposit + LibTokenizedVaultIO._externalDeposit(parentId, _externalTokenAddress, _depositAmount); + + // Execute the limit order + LibMarket._executeLimitOffer(parentId, _sellToken, _sellAmount, _buyToken, _buyAmount, LC.FEE_TYPE_TRADING); + } +} diff --git a/src/libs/LibACL.sol b/src/libs/LibACL.sol index 987cb295..07a3af74 100644 --- a/src/libs/LibACL.sol +++ b/src/libs/LibACL.sol @@ -7,10 +7,14 @@ import { LibHelpers } from "./LibHelpers.sol"; import { LibAdmin } from "./LibAdmin.sol"; import { LibObject } from "./LibObject.sol"; import { LibConstants } from "./LibConstants.sol"; -import { CannotUnassignRoleFromSelf, OwnerCannotBeSystemAdmin, RoleIsMissing, AssignerGroupIsMissing } from "../shared/CustomErrors.sol"; + +import { CannotUnassignRoleFromSelf, OwnerCannotBeSystemAdmin, RoleIsMissing, AssignerGroupIsMissing, InvalidGroupPrivilege } from "../shared/CustomErrors.sol"; + +import { LibString } from "solady/utils/LibString.sol"; library LibACL { - using LibHelpers for bytes32; + using LibString for *; + using LibHelpers for *; /** * @dev Emitted when a role gets updated. Empty roleId is assigned upon role removal @@ -129,6 +133,18 @@ library LibACL { return false; } + function _assertPriviledge(bytes32 _context, string memory _group) internal view { + if (!_hasGroupPrivilege(LibHelpers._getIdForAddress(msg.sender), _context, LibHelpers._stringToBytes32(_group))) + /// Note: If the role returned by `_getRoleInContext` is empty (represented by bytes32(0)), we explicitly return an empty string. + /// This ensures the user doesn't receive a string that could potentially include unwanted data (like pointer and length) without any meaningful content. + revert InvalidGroupPrivilege( + msg.sender._getIdForAddress(), + _context, + (_getRoleInContext(msg.sender._getIdForAddress(), _context) == bytes32(0)) ? "" : _getRoleInContext(msg.sender._getIdForAddress(), _context).fromSmallString(), + _group + ); + } + function _getRoleInContext(bytes32 _objectId, bytes32 _contextId) internal view returns (bytes32) { AppStorage storage s = LibAppStorage.diamondStorage(); return s.roles[_objectId][_contextId]; diff --git a/src/libs/LibAdmin.sol b/src/libs/LibAdmin.sol index 961fa2b1..f64d79a1 100644 --- a/src/libs/LibAdmin.sol +++ b/src/libs/LibAdmin.sol @@ -2,38 +2,40 @@ pragma solidity 0.8.20; import { AppStorage, FunctionLockedStorage, LibAppStorage } from "../shared/AppStorage.sol"; -import { Entity, EntityApproval } from "../shared/FreeStructs.sol"; +import { Entity, OnboardingApproval } from "../shared/FreeStructs.sol"; import { LibConstants as LC } from "./LibConstants.sol"; import { LibHelpers } from "./LibHelpers.sol"; import { LibObject } from "./LibObject.sol"; import { LibERC20 } from "./LibERC20.sol"; import { LibEntity } from "./LibEntity.sol"; import { LibACL } from "./LibACL.sol"; +import { LibEIP712 } from "./LibEIP712.sol"; + +import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import { MessageHashUtils } from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; + // prettier-ignore import { - CannotAddNullDiscountToken, - CannotAddNullSupportedExternalToken, CannotSupportExternalTokenWithMoreThan18Decimals, ObjectTokenSymbolAlreadyInUse, MinimumSellCannotBeZero, - EntityExistsAlready, - EntityOnboardingAlreadyApproved, EntityOnboardingNotApproved, InvalidSelfOnboardRoleApproval, - InvalidEntityId + InvalidSignatureError, + InvalidSignatureSError, + InvalidSignatureLength } from "../shared/CustomErrors.sol"; import { IDiamondProxy } from "src/generated/IDiamondProxy.sol"; library LibAdmin { + using ECDSA for bytes32; + event MaxDividendDenominationsUpdated(uint8 oldMax, uint8 newMax); event SupportedTokenAdded(address indexed tokenAddress); event FunctionsLocked(bytes4[] functionSelectors); event FunctionsUnlocked(bytes4[] functionSelectors); - event ObjectMinimumSellUpdated(bytes32 objectId, uint256 newMinimumSell); - event SelfOnboardingApproved(address indexed userAddress); event SelfOnboardingCompleted(address indexed userAddress); - event SelfOnboardingCancelled(address indexed userAddress); /// @notice The minimum amount of an object (par token, external token) that can be sold on the market event MinimumSellUpdated(bytes32 objectId, uint256 minimumSell); @@ -146,8 +148,11 @@ library LibAdmin { s.locked[IDiamondProxy.cancelSimplePolicy.selector] = true; s.locked[IDiamondProxy.createSimplePolicy.selector] = true; s.locked[IDiamondProxy.createEntity.selector] = true; + s.locked[IDiamondProxy.compoundRewards.selector] = true; + s.locked[IDiamondProxy.zapStake.selector] = true; + s.locked[IDiamondProxy.zapOrder.selector] = true; - bytes4[] memory lockedFunctions = new bytes4[](22); + bytes4[] memory lockedFunctions = new bytes4[](25); lockedFunctions[0] = IDiamondProxy.startTokenSale.selector; lockedFunctions[1] = IDiamondProxy.paySimpleClaim.selector; lockedFunctions[2] = IDiamondProxy.paySimplePremium.selector; @@ -170,6 +175,9 @@ library LibAdmin { lockedFunctions[19] = IDiamondProxy.createSimplePolicy.selector; lockedFunctions[20] = IDiamondProxy.createEntity.selector; lockedFunctions[21] = IDiamondProxy.collectRewardsToInterval.selector; + lockedFunctions[22] = IDiamondProxy.compoundRewards.selector; + lockedFunctions[23] = IDiamondProxy.zapStake.selector; + lockedFunctions[24] = IDiamondProxy.zapOrder.selector; emit FunctionsLocked(lockedFunctions); } @@ -198,8 +206,11 @@ library LibAdmin { s.locked[IDiamondProxy.createSimplePolicy.selector] = false; s.locked[IDiamondProxy.createEntity.selector] = false; s.locked[IDiamondProxy.collectRewardsToInterval.selector] = false; + s.locked[IDiamondProxy.compoundRewards.selector] = false; + s.locked[IDiamondProxy.zapStake.selector] = false; + s.locked[IDiamondProxy.zapOrder.selector] = false; - bytes4[] memory lockedFunctions = new bytes4[](22); + bytes4[] memory lockedFunctions = new bytes4[](25); lockedFunctions[0] = IDiamondProxy.startTokenSale.selector; lockedFunctions[1] = IDiamondProxy.paySimpleClaim.selector; lockedFunctions[2] = IDiamondProxy.paySimplePremium.selector; @@ -222,87 +233,100 @@ library LibAdmin { lockedFunctions[19] = IDiamondProxy.createSimplePolicy.selector; lockedFunctions[20] = IDiamondProxy.createEntity.selector; lockedFunctions[21] = IDiamondProxy.collectRewardsToInterval.selector; + lockedFunctions[22] = IDiamondProxy.compoundRewards.selector; + lockedFunctions[23] = IDiamondProxy.zapStake.selector; + lockedFunctions[24] = IDiamondProxy.zapOrder.selector; emit FunctionsUnlocked(lockedFunctions); } - function _approveSelfOnboarding(address _userAddress, bytes32 _entityId, bytes32 _roleId) internal { + function _onboardUserViaSignature(OnboardingApproval memory _approval) internal { AppStorage storage s = LibAppStorage.diamondStorage(); - // The entityId must be the valid type (entity). - if (!LibObject._isObjectType(_entityId, LC.OBJECT_TYPE_ENTITY)) revert InvalidEntityId(_entityId); + address userAddress = msg.sender; + + bytes32 entityId = _approval.entityId; + bytes32 roleId = _approval.roleId; + bytes memory sig = _approval.signature; - // Require that the user is not approved for the role already - if (_isSelfOnboardingApproved(_userAddress, _entityId, _roleId)) revert EntityOnboardingAlreadyApproved(_userAddress); + if (entityId == 0 || roleId == 0 || sig.length == 0) revert EntityOnboardingNotApproved(userAddress); - bool isTokenHolder = _roleId == LibHelpers._stringToBytes32(LC.ROLE_ENTITY_TOKEN_HOLDER); - bool isCapitalProvider = _roleId == LibHelpers._stringToBytes32(LC.ROLE_ENTITY_CP); + bool isTokenHolder = roleId == LibHelpers._stringToBytes32(LC.ROLE_ENTITY_TOKEN_HOLDER); + bool isCapitalProvider = roleId == LibHelpers._stringToBytes32(LC.ROLE_ENTITY_CP); if (!isTokenHolder && !isCapitalProvider) { - revert InvalidSelfOnboardRoleApproval(_roleId); + revert InvalidSelfOnboardRoleApproval(roleId); } - s.selfOnboarding[_userAddress] = EntityApproval({ entityId: _entityId, roleId: _roleId }); - - emit SelfOnboardingApproved(_userAddress); - } - - function _onboardUser(address _userAddress) internal { - AppStorage storage s = LibAppStorage.diamondStorage(); - EntityApproval memory approval = s.selfOnboarding[_userAddress]; + bytes32 signingHash = _getOnboardingHash(userAddress, entityId, roleId); + bytes32 signerId = LibHelpers._getIdForAddress(_getSigner(signingHash, sig)); - if (approval.entityId == 0 || approval.roleId == 0) { - revert EntityOnboardingNotApproved(_userAddress); + if (!LibACL._isInGroup(signerId, LibAdmin._getSystemId(), LibHelpers._stringToBytes32(LC.GROUP_ONBOARDING_APPROVERS))) { + revert EntityOnboardingNotApproved(userAddress); } - bytes32 userId = LibHelpers._getIdForAddress(_userAddress); - - if (!s.existingEntities[approval.entityId]) { + if (!s.existingEntities[entityId]) { Entity memory entity; - LibEntity._createEntity(approval.entityId, userId, entity, 0); - } - - if (s.roles[approval.entityId][approval.entityId] != 0) { - LibACL._unassignRole(approval.entityId, approval.entityId); + bytes32 userId = LibHelpers._getIdForAddress(userAddress); + LibEntity._createEntity(entityId, userId, entity, 0); } - if (s.roles[approval.entityId][LibAdmin._getSystemId()] != 0) { - LibACL._unassignRole(approval.entityId, LibAdmin._getSystemId()); + if (s.roles[entityId][LibAdmin._getSystemId()] != 0) { + LibACL._unassignRole(entityId, LibAdmin._getSystemId()); } + LibACL._assignRole(entityId, LibAdmin._getSystemId(), roleId); - LibACL._assignRole(approval.entityId, LibAdmin._getSystemId(), approval.roleId); - LibACL._assignRole(approval.entityId, approval.entityId, approval.roleId); - - delete s.selfOnboarding[_userAddress]; - - emit SelfOnboardingCompleted(_userAddress); + emit SelfOnboardingCompleted(userAddress); } - function _isSelfOnboardingApproved(address _userAddress, bytes32 _entityId, bytes32 _roleId) internal view returns (bool) { + function _setMinimumSell(bytes32 _objectId, uint256 _minimumSell) internal { AppStorage storage s = LibAppStorage.diamondStorage(); + if (_minimumSell == 0) revert MinimumSellCannotBeZero(); - EntityApproval memory approval = s.selfOnboarding[_userAddress]; + s.objectMinimumSell[_objectId] = _minimumSell; - return approval.entityId == _entityId && approval.roleId == _roleId; + emit MinimumSellUpdated(_objectId, _minimumSell); } - function _cancelSelfOnboarding(address _userAddress) internal { - AppStorage storage s = LibAppStorage.diamondStorage(); + function _getOnboardingHash(address _userAddress, bytes32 _entityId, bytes32 _roleId) internal view returns (bytes32) { + return + LibEIP712._hashTypedDataV4( + keccak256(abi.encode(keccak256("OnboardingApproval(address _userAddress,bytes32 _entityId,bytes32 _roleId)"), _userAddress, _entityId, _roleId)) + ); + } - if (s.selfOnboarding[_userAddress].entityId == 0 && s.selfOnboarding[_userAddress].roleId == 0) { - revert EntityOnboardingNotApproved(_userAddress); - } + function _getSigner(bytes32 signingHash, bytes memory signature) internal pure returns (address) { + bytes32 r; + bytes32 s; + uint8 v; - delete s.selfOnboarding[_userAddress]; + // ecrecover takes the signature parameters, and the only way to get them + if (signature.length != 65) { + revert InvalidSignatureLength(); + } - emit SelfOnboardingCancelled(_userAddress); - } + // currently is to use assembly. + /// @solidity memory-safe-assembly + assembly { + r := mload(add(signature, 0x20)) + s := mload(add(signature, 0x40)) + v := byte(0, mload(add(signature, 0x60))) + + switch v + // if v == 0, then v = 27 + case 0 { + v := 27 + } + // if v == 1, then v = 28 + case 1 { + v := 28 + } + } - function _setMinimumSell(bytes32 _objectId, uint256 _minimumSell) internal { - AppStorage storage s = LibAppStorage.diamondStorage(); - if (_minimumSell == 0) revert MinimumSellCannotBeZero(); + (address signer, ECDSA.RecoverError err, ) = ECDSA.tryRecover(MessageHashUtils.toEthSignedMessageHash(signingHash), v, r, s); - s.objectMinimumSell[_objectId] = _minimumSell; + if (err == ECDSA.RecoverError.InvalidSignature) revert InvalidSignatureError(signingHash); + else if (err == ECDSA.RecoverError.InvalidSignatureS) revert InvalidSignatureSError(s); - emit MinimumSellUpdated(_objectId, _minimumSell); + return signer; } } diff --git a/src/libs/LibConstants.sol b/src/libs/LibConstants.sol index f510f12b..f181be43 100644 --- a/src/libs/LibConstants.sol +++ b/src/libs/LibConstants.sol @@ -50,19 +50,18 @@ library LibConstants { string internal constant ROLE_ENTITY_COMPTROLLER_CLAIM = "Comptroller Claim"; string internal constant ROLE_ENTITY_COMPTROLLER_DIVIDEND = "Comptroller Dividend"; - /// old roles + string internal constant ROLE_ONBOARDING_APPROVER = "Onboarding Approver"; + /// old roles string internal constant ROLE_SPONSOR = "Sponsor"; string internal constant ROLE_CAPITAL_PROVIDER = "Capital Provider"; string internal constant ROLE_INSURED_PARTY = "Insured"; string internal constant ROLE_BROKER = "Broker"; string internal constant ROLE_SERVICE_PROVIDER = "Service Provider"; - string internal constant ROLE_UNDERWRITER = "Underwriter"; string internal constant ROLE_CLAIMS_ADMIN = "Claims Admin"; string internal constant ROLE_TRADER = "Trader"; string internal constant ROLE_SEGREGATED_ACCOUNT = "Segregated Account"; - string internal constant ROLE_ONBOARDING_APPROVER = "Onboarding Approver"; /// Groups diff --git a/src/libs/LibHelpers.sol b/src/libs/LibHelpers.sol index d3cff670..a8b7c9d4 100644 --- a/src/libs/LibHelpers.sol +++ b/src/libs/LibHelpers.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.20; -import { LibConstants as LC } from "./LibConstants.sol"; - /// @notice Pure functions library LibHelpers { function _getIdForAddress(address _addr) internal pure returns (bytes32) { diff --git a/src/libs/LibInitDiamond.sol b/src/libs/LibInitDiamond.sol index 5e6ae5f1..45a641fb 100644 --- a/src/libs/LibInitDiamond.sol +++ b/src/libs/LibInitDiamond.sol @@ -38,6 +38,7 @@ library LibInitDiamond { LibACL._updateRoleGroup(LC.ROLE_SYSTEM_ADMIN, LC.GROUP_SYSTEM_ADMINS, true); LibACL._updateRoleGroup(LC.ROLE_SYSTEM_MANAGER, LC.GROUP_START_TOKEN_SALE, true); LibACL._updateRoleGroup(LC.ROLE_ENTITY_MANAGER, LC.GROUP_START_TOKEN_SALE, true); + LibACL._updateRoleGroup(LC.ROLE_ENTITY_ADMIN, LC.GROUP_CANCEL_OFFER, true); LibACL._updateRoleGroup(LC.ROLE_ENTITY_MANAGER, LC.GROUP_CANCEL_OFFER, true); LibACL._updateRoleGroup(LC.ROLE_ENTITY_CP, LC.GROUP_CANCEL_OFFER, true); LibACL._updateRoleGroup(LC.ROLE_ENTITY_CP, LC.GROUP_EXECUTE_LIMIT_OFFER, true); @@ -62,7 +63,7 @@ library LibInitDiamond { // setup stakeholder groups LibACL._updateRoleGroup(LC.ROLE_UNDERWRITER, LC.GROUP_UNDERWRITERS, true); LibACL._updateRoleGroup(LC.ROLE_BROKER, LC.GROUP_BROKERS, true); - LibACL._updateRoleGroup(LC.ROLE_CAPITAL_PROVIDER, LC.GROUP_CAPITAL_PROVIDERS, true); + LibACL._updateRoleGroup(LC.ROLE_ENTITY_CP, LC.GROUP_CAPITAL_PROVIDERS, true); LibACL._updateRoleGroup(LC.ROLE_INSURED_PARTY, LC.GROUP_INSURED_PARTIES, true); // setup assigners diff --git a/src/libs/LibMarket.sol b/src/libs/LibMarket.sol index 1657e484..88c42268 100644 --- a/src/libs/LibMarket.sol +++ b/src/libs/LibMarket.sol @@ -230,10 +230,8 @@ library LibMarket { if (_buyAmount * calcs.currentSellAmount < calcs.currentBuyAmount * _sellAmount) { if (buyExternalToken) { // Normalize the sell amount when taker is buying external tokens - calcs.normalizedBuyAmount = (_buyAmount * calcs.currentSellAmount) / _sellAmount; - calcs.normalizedSellAmount = calcs.currentSellAmount; - // if the taker is buying an external token, we need to normalize current buy amount value + // if the taker is buying an external token, we need to normalize current buy amount value // normalization factor = taker price / maker price: // = (initial buy amount/initial sell amount) / (current buy amount / current sell amount) // = initial buy amount * current sell amount / initial sell amount / current buy amount @@ -241,30 +239,26 @@ library LibMarket { // = current buy amount * normalization factor // normalized buy amount = current buy amount * (initial buy amount * current sell amount / initial sell amount / current buy amount) // which equals to below: - result.remainingBuyAmount -= (_buyAmount * calcs.currentSellAmount) / _sellAmount; - result.remainingSellAmount -= calcs.currentSellAmount; + calcs.normalizedBuyAmount = (_buyAmount * calcs.currentSellAmount) / _sellAmount; + calcs.normalizedSellAmount = calcs.currentSellAmount; } else { // Normalize the sell amount when taker is buying participation tokens - calcs.normalizedSellAmount = (_sellAmount * calcs.currentBuyAmount) / _buyAmount; - calcs.normalizedBuyAmount = calcs.currentBuyAmount; // if the taker is buying participation tokens we need to normalize current sell amount value - result.remainingBuyAmount -= calcs.currentBuyAmount; - result.remainingSellAmount -= (_sellAmount * calcs.currentBuyAmount) / _buyAmount; + calcs.normalizedBuyAmount = calcs.currentBuyAmount; + calcs.normalizedSellAmount = (_sellAmount * calcs.currentBuyAmount) / _buyAmount; } - - emit OrderMatched(_offerId, bestOfferId, calcs.normalizedSellAmount, calcs.normalizedBuyAmount); // taker offer - emit OrderMatched(bestOfferId, _offerId, calcs.normalizedBuyAmount, calcs.normalizedSellAmount); // maker offer + result.remainingBuyAmount -= calcs.normalizedBuyAmount; + result.remainingSellAmount -= calcs.normalizedSellAmount; } else { result.remainingBuyAmount -= calcs.currentBuyAmount; result.remainingSellAmount -= calcs.currentSellAmount; - - emit OrderMatched(_offerId, bestOfferId, calcs.currentSellAmount, calcs.currentBuyAmount); // taker offer - emit OrderMatched(bestOfferId, _offerId, calcs.currentBuyAmount, calcs.currentSellAmount); // maker offer } // note: events are emmited to keep track of average price actually paid, // in case matched is done with more preferable offers, otherwise this information is be lost + emit OrderMatched(_offerId, bestOfferId, calcs.currentSellAmount, calcs.currentBuyAmount); // taker offer + emit OrderMatched(bestOfferId, _offerId, calcs.currentBuyAmount, calcs.currentSellAmount); // maker offer } } diff --git a/src/libs/LibSimplePolicy.sol b/src/libs/LibSimplePolicy.sol index 6fc817f1..d4ef2638 100644 --- a/src/libs/LibSimplePolicy.sol +++ b/src/libs/LibSimplePolicy.sol @@ -10,12 +10,10 @@ import { LibTokenizedVault } from "./LibTokenizedVault.sol"; import { LibFeeRouter } from "./LibFeeRouter.sol"; import { LibHelpers } from "./LibHelpers.sol"; import { LibEIP712 } from "./LibEIP712.sol"; -import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import { MessageHashUtils } from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; -import { ExcessiveCommissionReceivers, FeeBasisPointsExceedHalfMax, EntityDoesNotExist, PolicyDoesNotExist, PolicyCannotCancelAfterMaturation, PolicyIdCannotBeZero, DuplicateSignerCreatingSimplePolicy, SimplePolicyStakeholderSignatureInvalid, SimplePolicyClaimsPaidShouldStartAtZero, SimplePolicyPremiumsPaidShouldStartAtZero, CancelCannotBeTrueWhenCreatingSimplePolicy, InvalidSignatureError, InvalidSignatureSError, MaturationDateTooFar } from "../shared/CustomErrors.sol"; + +import { ExcessiveCommissionReceivers, FeeBasisPointsExceedHalfMax, EntityDoesNotExist, PolicyDoesNotExist, PolicyCannotCancelAfterMaturation, PolicyIdCannotBeZero, DuplicateSignerCreatingSimplePolicy, SimplePolicyStakeholderSignatureInvalid, SimplePolicyClaimsPaidShouldStartAtZero, SimplePolicyPremiumsPaidShouldStartAtZero, CancelCannotBeTrueWhenCreatingSimplePolicy, MaturationDateTooFar } from "../shared/CustomErrors.sol"; library LibSimplePolicy { - using ECDSA for bytes32; /** * @notice New policy has been created * @dev Emitted when policy is created @@ -68,7 +66,7 @@ library LibSimplePolicy { for (uint256 i = 0; i < rolesCount; i++) { previousSigner = signer; - signer = getSigner(signingHash, _stakeholders.signatures[i]); + signer = LibAdmin._getSigner(signingHash, _stakeholders.signatures[i]); if (LibObject._getParentFromAddress(signer) != _stakeholders.entityIds[i]) { revert SimplePolicyStakeholderSignatureInvalid( @@ -92,39 +90,6 @@ library LibSimplePolicy { emit SimplePolicyCreated(_policyId, _entityId); } - function getSigner(bytes32 signingHash, bytes memory signature) private pure returns (address) { - bytes32 r; - bytes32 s; - uint8 v; - - // ecrecover takes the signature parameters, and the only way to get them - if (signature.length == 65) { - // currently is to use assembly. - /// @solidity memory-safe-assembly - assembly { - r := mload(add(signature, 0x20)) - s := mload(add(signature, 0x40)) - v := byte(0, mload(add(signature, 0x60))) - - switch v - // if v == 0, then v = 27 - case 0 { - v := 27 - } - // if v == 1, then v = 28 - case 1 { - v := 28 - } - } - } - - (address signer, ECDSA.RecoverError err, ) = ECDSA.tryRecover(MessageHashUtils.toEthSignedMessageHash(signingHash), v, r, s); - - if (err == ECDSA.RecoverError.InvalidSignature) revert InvalidSignatureError(signingHash); - else if (err == ECDSA.RecoverError.InvalidSignatureS) revert InvalidSignatureSError(s); - - return signer; - } /** * @dev If an entity passes their checks to create a policy, ensure that the entity's capacity is appropriately decreased by the amount of capital that will be tied to the new policy being created. */ diff --git a/src/libs/LibTokenizedVault.sol b/src/libs/LibTokenizedVault.sol index 9462129a..1b0ab30f 100644 --- a/src/libs/LibTokenizedVault.sol +++ b/src/libs/LibTokenizedVault.sol @@ -7,7 +7,7 @@ import { LibConstants as LC } from "./LibConstants.sol"; import { LibHelpers } from "./LibHelpers.sol"; import { LibObject } from "./LibObject.sol"; import { LibERC20 } from "./LibERC20.sol"; -import { RebasingInterestNotInitialized, RebasingInterestInsufficient, RebasingAmountInvalid, RebasingSupplyDecreased } from "../shared/CustomErrors.sol"; +import { RebasingInterestNotInitialized, RebasingInterestInsufficient, RebasingSupplyDecreased } from "../shared/CustomErrors.sol"; import { InsufficientBalance } from "../shared/CustomErrors.sol"; @@ -189,7 +189,7 @@ library LibTokenizedVault { if (withdrawableDividend > 0) { // Bump the withdrawn dividends for the owner /// Special Case: (_tokenId == _dividendTokenId), i.e distributing accrued interest for rebasing coins like USDM - /// withdrawnDividendPerOwner should be adjusted before tha update, so that the user cannot claim additional dividend based on the amount he just received as dividend + /// withdrawnDividendPerOwner should be adjusted before the update, so that the user cannot claim additional dividend based on the amount he just received as dividend /// dividend is calculated based on a ratio between users balance and the total, but in this case claiming the dividend his balance increases and /// thus his share of the total increases as well, which entitles him to claim more of the dividend, potentially draining out the entirety of it if repeated infinitely if (_tokenId == _dividendTokenId) { @@ -297,6 +297,13 @@ library LibTokenizedVault { return s.lockedBalances[_accountId][_tokenId]; } + function _getAvailableBalance(bytes32 _accountId, bytes32 _tokenId) internal view returns (uint256 amount) { + AppStorage storage s = LibAppStorage.diamondStorage(); + uint256 lockedBalance = s.lockedBalances[_accountId][_tokenId]; + uint256 internalBalance = s.tokenBalances[_tokenId][_accountId]; + return internalBalance - lockedBalance; + } + function _totalDividends(bytes32 _tokenId, bytes32 _dividendDenominationId) internal view returns (uint256) { AppStorage storage s = LibAppStorage.diamondStorage(); diff --git a/src/libs/LibTokenizedVaultStaking.sol b/src/libs/LibTokenizedVaultStaking.sol index 6fb3d607..352baaa2 100644 --- a/src/libs/LibTokenizedVaultStaking.sol +++ b/src/libs/LibTokenizedVaultStaking.sol @@ -317,6 +317,28 @@ library LibTokenizedVaultStaking { s.stakeBalance[vTokenId][_stakerId] = state.balance; } + function _compoundRewards(bytes32 _stakerId, bytes32 _entityId, uint64 _interval) internal { + AppStorage storage s = LibAppStorage.diamondStorage(); + bytes32 tokenId = s.stakingConfigs[_entityId].tokenId; + + (, RewardsBalances memory rewards) = _getStakingStateWithRewardsBalances(_stakerId, _entityId, _interval); + + uint256 rewardAmount; + uint256 rewardCount = rewards.currencies.length; + + for (uint64 i = 0; i < rewardCount; i++) { + if (rewards.currencies[i] == tokenId) { + rewardAmount = rewards.amounts[i]; + break; + } + } + + require(rewardAmount > 0, "No reward to compound"); + + _collectRewards(_stakerId, _entityId, _interval); + _stake(_stakerId, _entityId, rewardAmount); + } + function _collectRewards(bytes32 _stakerId, bytes32 _entityId, uint64 _interval) internal { AppStorage storage s = LibAppStorage.diamondStorage(); diff --git a/src/shared/CustomErrors.sol b/src/shared/CustomErrors.sol index ad8de610..ff2471ff 100644 --- a/src/shared/CustomErrors.sol +++ b/src/shared/CustomErrors.sol @@ -29,15 +29,9 @@ error InvalidGroupPrivilege(bytes32 msgSenderId, bytes32 context, string roleInC /// @param role The name of the rle which should not be approaved for self-onboarding error InvalidSelfOnboardRoleApproval(bytes32 role); -/// @dev Passing in a missing address when trying to add a token address to the supported external token list. -error CannotAddNullSupportedExternalToken(); - /// @dev Cannot add a ERC20 token to the supported external token list that has more than 18 decimal places. error CannotSupportExternalTokenWithMoreThan18Decimals(); -/// @dev Passing in a missing address when trying to assign a new token address as the new discount token. -error CannotAddNullDiscountToken(); - /// @dev Object exsists when it should not. error ObjectExistsAlready(bytes32 objectId); @@ -50,9 +44,6 @@ error EntityDoesNotExist(bytes32 objectId); /// @dev The entity self onboarding not approved error EntityOnboardingNotApproved(address userAddress); -/// @dev The entity self onboarding already approved -error EntityOnboardingAlreadyApproved(address userAddress); - /// @dev Cannot create an entity that already exists. error EntityExistsAlready(bytes32 entityId); @@ -129,9 +120,6 @@ error RebasingInterestNotInitialized(bytes32 tokenId); /// @dev Insufficient amount of interest acrrued so far error RebasingInterestInsufficient(bytes32 tokenId, uint256 amount, uint256 accruedAmount); -/// @dev Rebase amount cannot be greater than the actual balance -error RebasingAmountInvalid(bytes32 tokenId, uint256 amount, uint256 currentBalance); - /// @dev Staking can be initialized only once error StakingAlreadyStarted(bytes32 entityId, bytes32 tokenId); @@ -179,6 +167,10 @@ error RebasingSupplyDecreased(bytes32 tokenId, uint256 accountInNayms, uint256 a /// @notice This error suggests that the signature itself is malformed or does not correspond to the hash provided. error InvalidSignatureError(bytes32 hash); +/// @dev This error indicates that the signatures is not 65 bytes. +/// @notice This error indicates invalid signature length. +error InvalidSignatureLength(); + /// @dev This error is used to indicate that the signature has an invalid 's' value. /// @param sValue The 's' value of the ECDSA signature that was deemed invalid. /// @notice This error is triggered when the 's' value of the signature is not within the lower half of the secp256k1 curve's order, which can lead to malleability issues. @@ -187,9 +179,6 @@ error InvalidSignatureSError(bytes32 sValue); /// @dev Thrown when the number of receivers specified in a transaction is not within the acceptable range. error InvalidReceiverCount(uint256 numberOfReceivers); -/// @dev The entity ID is invalid for the given context. -error InvalidEntityId(bytes32 entityId); - /// @dev Thrown when the maturation date of a policy is set beyond the allowable future date limit. /// This prevents setting unrealistic maturation dates that could affect the contract's operability or the enforceability of the policy. error MaturationDateTooFar(uint256 maturationDate); @@ -212,3 +201,8 @@ error InvalidTokenId(); /// @dev Cannot stake an amount lower than objectMinimumSell[tokenId]. error InvalidStakingAmount(); + +/// @dev Not a supported external ERC 20 token +/// @param tokenAddress Address of the ERC20 token +/// @param method Name of the method that threw the error +error InvalidERC20Token(address tokenAddress, string method); diff --git a/src/shared/FreeStructs.sol b/src/shared/FreeStructs.sol index a10206ca..68f419d3 100644 --- a/src/shared/FreeStructs.sol +++ b/src/shared/FreeStructs.sol @@ -40,6 +40,7 @@ struct Entity { bool simplePolicyEnabled; } +// DEPRECATED, but don't remove, referenced in appstorage struct EntityApproval { bytes32 entityId; bytes32 roleId; @@ -126,3 +127,16 @@ struct RewardsBalances { uint256[] amounts; uint64 lastPaidInterval; } + +struct PermitSignature { + uint256 deadline; + uint8 v; + bytes32 r; + bytes32 s; +} + +struct OnboardingApproval { + bytes32 entityId; + bytes32 roleId; + bytes signature; +} diff --git a/src/shared/Modifiers.sol b/src/shared/Modifiers.sol index aabe2f91..94a6d6ed 100644 --- a/src/shared/Modifiers.sol +++ b/src/shared/Modifiers.sol @@ -4,12 +4,8 @@ pragma solidity 0.8.20; /// @notice modifiers import { LibAdmin } from "../libs/LibAdmin.sol"; -import { LibConstants as LC } from "../libs/LibConstants.sol"; -import { LibHelpers } from "../libs/LibHelpers.sol"; -import { LibObject } from "../libs/LibObject.sol"; import { LibACL } from "../libs/LibACL.sol"; -import { InvalidGroupPrivilege } from "./CustomErrors.sol"; -import { LibString } from "solady/utils/LibString.sol"; +import { LibObject } from "../libs/LibObject.sol"; /** * @title Modifiers @@ -17,25 +13,13 @@ import { LibString } from "solady/utils/LibString.sol"; * @dev Function modifiers to control access */ contract Modifiers { - using LibHelpers for *; - using LibACL for *; - using LibString for *; - modifier notLocked() { require(!LibAdmin._isFunctionLocked(msg.sig), "function is locked"); _; } modifier assertPrivilege(bytes32 _context, string memory _group) { - if (!msg.sender._getIdForAddress()._hasGroupPrivilege(_context, _group._stringToBytes32())) - /// Note: If the role returned by `_getRoleInContext` is empty (represented by bytes32(0)), we explicitly return an empty string. - /// This ensures the user doesn't receive a string that could potentially include unwanted data (like pointer and length) without any meaningful content. - revert InvalidGroupPrivilege( - msg.sender._getIdForAddress(), - _context, - (msg.sender._getIdForAddress()._getRoleInContext(_context) == bytes32(0)) ? "" : msg.sender._getIdForAddress()._getRoleInContext(_context).fromSmallString(), - _group - ); + LibACL._assertPriviledge(_context, _group); _; } diff --git a/test/T01Deployment.t.sol b/test/T01Deployment.t.sol index 1bcb11f3..bb2e5284 100644 --- a/test/T01Deployment.t.sol +++ b/test/T01Deployment.t.sol @@ -8,7 +8,6 @@ import { D03ProtocolDefaults } from "./defaults/D03ProtocolDefaults.sol"; import { InitDiamondFixture } from "./fixtures/InitDiamondFixture.sol"; import { IDiamondLoupe } from "lib/diamond-2-hardhat/contracts/interfaces/IDiamondLoupe.sol"; import { IDiamondCut } from "lib/diamond-2-hardhat/contracts/interfaces/IDiamondCut.sol"; -import { IDiamondProxy } from "src/generated/IDiamondProxy.sol"; import { DiamondAlreadyInitialized } from "src/init/InitDiamond.sol"; import { LibGovernance } from "src/libs/LibGovernance.sol"; diff --git a/test/T01LibERC20.t.sol b/test/T01LibERC20.t.sol index b9efd51e..fe92f56b 100644 --- a/test/T01LibERC20.t.sol +++ b/test/T01LibERC20.t.sol @@ -2,7 +2,6 @@ pragma solidity 0.8.20; // solhint-disable no-global-import -import { Vm } from "forge-std/Vm.sol"; import { D03ProtocolDefaults } from "./defaults/D03ProtocolDefaults.sol"; import { DummyToken } from "./utils/DummyToken.sol"; import { BadToken } from "./utils/BadToken.sol"; @@ -81,10 +80,6 @@ contract T01LibERC20 is D03ProtocolDefaults { vm.expectRevert("not enough balance"); fixture.transfer(tokenAddress, account0, 101); - // failed transfer of 0 - vm.expectRevert("LibERC20: transfer or transferFrom returned false"); - fixture.transfer(tokenAddress, account0, 0); - // successful transfer fixture.transfer(tokenAddress, account0, 100); @@ -113,10 +108,6 @@ contract T01LibERC20 is D03ProtocolDefaults { vm.expectRevert("not enough balance"); fixture.transferFrom(tokenAddress, signer1, account0, 101); - // failed transfer of 0 reverts with empty string - vm.expectRevert("LibERC20: transfer or transferFrom reverted"); - fixture.transferFrom(tokenAddress, signer1, account0, 0); - // successful transfer fixture.transferFrom(tokenAddress, signer1, account0, 100); diff --git a/test/T01LibHelpers.t.sol b/test/T01LibHelpers.t.sol index 26fbf15e..ae71680b 100644 --- a/test/T01LibHelpers.t.sol +++ b/test/T01LibHelpers.t.sol @@ -2,7 +2,6 @@ pragma solidity 0.8.20; import { Test } from "forge-std/Test.sol"; -import { Vm } from "forge-std/Vm.sol"; import { LibHelpers } from "../src/libs/LibHelpers.sol"; contract T01LibHelpers is Test { diff --git a/test/T02ACL.t.sol b/test/T02ACL.t.sol index 733369a3..2e71e8f6 100644 --- a/test/T02ACL.t.sol +++ b/test/T02ACL.t.sol @@ -1,6 +1,5 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.20; -import { console2 } from "forge-std/console2.sol"; import { D03ProtocolDefaults, LibHelpers, LC } from "./defaults/D03ProtocolDefaults.sol"; import { MockAccounts } from "test/utils/users/MockAccounts.sol"; import { Vm } from "forge-std/Vm.sol"; @@ -92,26 +91,11 @@ contract T02ACLTest is D03ProtocolDefaults, MockAccounts { string memory role = LC.ROLE_ENTITY_ADMIN; vm.recordLogs(); - nayms.assignRole(signer1Id, context, role); Vm.Log[] memory entries = vm.getRecordedLogs(); - - assertEq(entries[0].topics.length, 2); - assertEq(entries[0].topics[0], keccak256("RoleUpdated(bytes32,bytes32,bytes32,string)")); - assertEq(entries[0].topics[1], signer1Id); - (bytes32 contextId, bytes32 roleId, string memory action) = abi.decode(entries[0].data, (bytes32, bytes32, string)); - assertEq(contextId, context); - assertEq(roleId, bytes32(0)); - assertEq(action, "_unassignRole"); - - assertEq(entries[1].topics.length, 2); - assertEq(entries[1].topics[0], keccak256("RoleUpdated(bytes32,bytes32,bytes32,string)")); - assertEq(entries[1].topics[1], signer1Id); - (contextId, roleId, action) = abi.decode(entries[1].data, (bytes32, bytes32, string)); - assertEq(contextId, context); - assertEq(roleId, LibHelpers._stringToBytes32(role)); - assertEq(action, "_assignRole"); + assertRoleUpdateEvent(entries, 0, context, signer1Id, bytes32(0), "_unassignRole"); + assertRoleUpdateEvent(entries, 1, context, signer1Id, LibHelpers._stringToBytes32(role), "_assignRole"); } function testInvalidObjectIdWhenAssignRole() public { diff --git a/test/T02Access.t.sol b/test/T02Access.t.sol index e46fb6ba..89864d29 100644 --- a/test/T02Access.t.sol +++ b/test/T02Access.t.sol @@ -1,14 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.20; -// import { c as c } from "forge-std/c.sol"; + import { D03ProtocolDefaults, LibHelpers, LC, c } from "./defaults/D03ProtocolDefaults.sol"; import { Entity, SimplePolicy, Stakeholders } from "src/shared/FreeStructs.sol"; import "src/shared/CustomErrors.sol"; -// updateRoleGroup | isRoleInGroup | groups [role][group] = bool -// updateRoleAssigner | canGroupAssignRole | canAssign [role] = group -// getRoleInContext | roles[objectId][contextId] = role - contract T02Access is D03ProtocolDefaults { using LibHelpers for *; diff --git a/test/T02Admin.t.sol b/test/T02Admin.t.sol index 1d0c969e..2ffff6df 100644 --- a/test/T02Admin.t.sol +++ b/test/T02Admin.t.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.20; import { D03ProtocolDefaults, LibHelpers, LC } from "./defaults/D03ProtocolDefaults.sol"; -import { Entity, Stakeholders, SimplePolicy } from "../src/shared/FreeStructs.sol"; +import { Entity, Stakeholders, SimplePolicy, PermitSignature, OnboardingApproval } from "../src/shared/FreeStructs.sol"; import { MockAccounts } from "test/utils/users/MockAccounts.sol"; import { Vm } from "forge-std/Vm.sol"; import { IDiamondProxy } from "../src/generated/IDiamondProxy.sol"; @@ -241,7 +241,7 @@ contract T02AdminTest is D03ProtocolDefaults, MockAccounts { assertEq(entries[0].topics[0], keccak256("FunctionsLocked(bytes4[])")); (s_functionSelectors) = abi.decode(entries[0].data, (bytes4[])); - bytes4[] memory lockedFunctions = new bytes4[](22); + bytes4[] memory lockedFunctions = new bytes4[](25); lockedFunctions[0] = IDiamondProxy.startTokenSale.selector; lockedFunctions[1] = IDiamondProxy.paySimpleClaim.selector; lockedFunctions[2] = IDiamondProxy.paySimplePremium.selector; @@ -264,6 +264,9 @@ contract T02AdminTest is D03ProtocolDefaults, MockAccounts { lockedFunctions[19] = IDiamondProxy.createSimplePolicy.selector; lockedFunctions[20] = IDiamondProxy.createEntity.selector; lockedFunctions[21] = IDiamondProxy.collectRewardsToInterval.selector; + lockedFunctions[22] = IDiamondProxy.compoundRewards.selector; + lockedFunctions[23] = IDiamondProxy.zapStake.selector; + lockedFunctions[24] = IDiamondProxy.zapOrder.selector; for (uint256 i = 0; i < lockedFunctions.length; i++) { assertTrue(nayms.isFunctionLocked(lockedFunctions[i])); @@ -323,6 +326,18 @@ contract T02AdminTest is D03ProtocolDefaults, MockAccounts { vm.expectRevert("function is locked"); nayms.collectRewards(bytes32(0)); + vm.expectRevert("function is locked"); + nayms.compoundRewards(bytes32(0)); + + PermitSignature memory permSig; + OnboardingApproval memory onboardingApproval; + + vm.expectRevert("function is locked"); + nayms.zapStake(address(0), bytes32(0), 0, 0, permSig, onboardingApproval); + + vm.expectRevert("function is locked"); + nayms.zapOrder(address(0), 0, bytes32(0), 0, bytes32(0), 0, permSig, onboardingApproval); + vm.expectRevert("function is locked"); nayms.collectRewardsToInterval(bytes32(0), 5); @@ -359,8 +374,11 @@ contract T02AdminTest is D03ProtocolDefaults, MockAccounts { assertFalse(nayms.isFunctionLocked(IDiamondProxy.payReward.selector), "function payReward locked"); assertFalse(nayms.isFunctionLocked(IDiamondProxy.collectRewards.selector), "function collectRewards locked"); assertFalse(nayms.isFunctionLocked(IDiamondProxy.collectRewardsToInterval.selector), "function collectRewardsToInterval locked"); + assertFalse(nayms.isFunctionLocked(IDiamondProxy.compoundRewards.selector), "function compoundRewards locked"); assertFalse(nayms.isFunctionLocked(IDiamondProxy.cancelSimplePolicy.selector), "function cancelSimplePolicy locked"); assertFalse(nayms.isFunctionLocked(IDiamondProxy.createSimplePolicy.selector), "function createSimplePolicy locked"); assertFalse(nayms.isFunctionLocked(IDiamondProxy.createEntity.selector), "function createEntity locked"); + assertFalse(nayms.isFunctionLocked(IDiamondProxy.zapStake.selector), "function zapStake locked"); + assertFalse(nayms.isFunctionLocked(IDiamondProxy.zapOrder.selector), "function zapOrder locked"); } } diff --git a/test/T02User.t.sol b/test/T02User.t.sol index a5e387ff..a02fa3d8 100644 --- a/test/T02User.t.sol +++ b/test/T02User.t.sol @@ -3,7 +3,6 @@ pragma solidity 0.8.20; import { D03ProtocolDefaults, LibHelpers, LC } from "./defaults/D03ProtocolDefaults.sol"; import { MockAccounts } from "test/utils/users/MockAccounts.sol"; -import { Vm } from "forge-std/Vm.sol"; import "../src/shared/CustomErrors.sol"; contract T02UserTest is D03ProtocolDefaults, MockAccounts { diff --git a/test/T03NaymsOwnership.t.sol b/test/T03NaymsOwnership.t.sol index fae797be..803742a4 100644 --- a/test/T03NaymsOwnership.t.sol +++ b/test/T03NaymsOwnership.t.sol @@ -48,10 +48,10 @@ contract T03NaymsOwnershipTest is D03ProtocolDefaults, MockAccounts { function testFuzz_TransferOwnership(address newOwner, address notSysAdmin, address anotherSysAdmin) public { vm.assume(newOwner != anotherSysAdmin && newOwner != account0 && newOwner != address(0) && anotherSysAdmin != address(0)); vm.assume(anotherSysAdmin != address(0)); + vm.assume(!nayms.isInGroup(newOwner._getIdForAddress(), systemContext, LC.GROUP_SYSTEM_MANAGERS)); - bytes32 notSysAdminId = LibHelpers._getIdForAddress(address(notSysAdmin)); // note: for this test, assume that the notSysAdmin address is not a system admin - vm.assume(!nayms.isInGroup(notSysAdminId, systemContext, LC.GROUP_SYSTEM_ADMINS)); + vm.assume(!nayms.isInGroup(notSysAdmin._getIdForAddress(), systemContext, LC.GROUP_SYSTEM_ADMINS)); vm.label(newOwner, "newOwner"); vm.label(notSysAdmin, "notSysAdmin"); diff --git a/test/T03SystemFacet.t.sol b/test/T03SystemFacet.t.sol index 39351c93..a7772f6c 100644 --- a/test/T03SystemFacet.t.sol +++ b/test/T03SystemFacet.t.sol @@ -2,10 +2,7 @@ pragma solidity 0.8.20; import { D03ProtocolDefaults, LibHelpers, LibAdmin, LC, c } from "./defaults/D03ProtocolDefaults.sol"; - import { MockAccounts } from "./utils/users/MockAccounts.sol"; - -import { Entity } from "../src/shared/AppStorage.sol"; import "../src/shared/CustomErrors.sol"; contract T03SystemFacetTest is D03ProtocolDefaults, MockAccounts { diff --git a/test/T03TokenizedVault.t.sol b/test/T03TokenizedVault.t.sol index 38df1bbf..23b821ca 100644 --- a/test/T03TokenizedVault.t.sol +++ b/test/T03TokenizedVault.t.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.20; import { MockAccounts } from "./utils/users/MockAccounts.sol"; import { c, D03ProtocolDefaults, LibHelpers, LC } from "./defaults/D03ProtocolDefaults.sol"; -import { Entity, CalculatedFees } from "../src/shared/AppStorage.sol"; +import { Entity } from "../src/shared/AppStorage.sol"; import { IDiamondCut } from "lib/diamond-2-hardhat/contracts/interfaces/IDiamondCut.sol"; import { TokenizedVaultFixture } from "test/fixtures/TokenizedVaultFixture.sol"; import "src/shared/CustomErrors.sol"; @@ -74,7 +74,7 @@ contract T03TokenizedVaultTest is D03ProtocolDefaults, MockAccounts { require(success, "Should get commissions from app storage"); } - function testGetLockedBalance() public { + function testGetLockedAndAvailableBalance() public { changePrank(sm.addr); bytes32 entityId = createTestEntity(account0Id); @@ -85,7 +85,10 @@ contract T03TokenizedVaultTest is D03ProtocolDefaults, MockAccounts { nayms.enableEntityTokenization(entityId, "Entity1", "Entity1 Token", 1); nayms.startTokenSale(entityId, 100, 100); - assertEq(nayms.getLockedBalance(entityId, entityId), 100); + uint256 internalBalance = nayms.internalBalanceOf(entityId, entityId); + uint256 lockedBalance = nayms.getLockedBalance(entityId, entityId); + assertEq(lockedBalance, 100, "invalid locked balance"); + assertEq(nayms.getAvailableBalance(entityId, entityId), internalBalance - lockedBalance, "invalid avaiable balance"); } function testSingleExternalDeposit() public { @@ -106,7 +109,7 @@ contract T03TokenizedVaultTest is D03ProtocolDefaults, MockAccounts { vm.expectRevert("extDeposit: invalid receiver"); nayms.externalDeposit(wethAddress, 1); - vm.expectRevert("extDeposit: invalid ERC20 token"); + vm.expectRevert(abi.encodeWithSelector(InvalidERC20Token.selector, address(0xBADAAAAAAAAA), "extDeposit")); nayms.externalDeposit(address(0xBADAAAAAAAAA), 1); // deposit to entity1 diff --git a/test/T04Entity.t.sol b/test/T04Entity.t.sol index 56a16f57..067433a2 100644 --- a/test/T04Entity.t.sol +++ b/test/T04Entity.t.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.20; import { Vm } from "forge-std/Vm.sol"; import { c, D03ProtocolDefaults, LibHelpers, LC } from "./defaults/D03ProtocolDefaults.sol"; -import { Entity, MarketInfo, SimplePolicy, SimplePolicyInfo, Stakeholders } from "src/shared/FreeStructs.sol"; +import { Entity, MarketInfo, SimplePolicy, SimplePolicyInfo, Stakeholders, OnboardingApproval } from "src/shared/FreeStructs.sol"; import { IDiamondCut } from "lib/diamond-2-hardhat/contracts/interfaces/IDiamondCut.sol"; import { StdStyle } from "forge-std/StdStyle.sol"; @@ -14,9 +14,9 @@ import { SimplePolicyFixture } from "test/fixtures/SimplePolicyFixture.sol"; // solhint-disable no-global-import import "../src/shared/CustomErrors.sol"; +// solhint-disable no-console import { StdStyle } from "forge-std/StdStyle.sol"; -// solhint-disable no-console contract T04EntityTest is D03ProtocolDefaults { using LibHelpers for *; using StdStyle for *; @@ -339,9 +339,9 @@ contract T04EntityTest is D03ProtocolDefaults { bytes32 signingHash = nayms.getSigningHash(simplePolicy.startDate, simplePolicy.maturationDate, simplePolicy.asset, simplePolicy.limit, testPolicyDataHash); bytes[] memory signatures = new bytes[](3); - signatures[0] = initPolicySig(0xACC1, signingHash); // 0x2337f702bc9A7D1f415050365634FEbEdf4054Be - signatures[1] = initPolicySig(0xACC2, signingHash); // 0x167D6b35e51df22f42c4F42f26d365756D244fDE - signatures[2] = initPolicySig(0xACC3, signingHash); // 0x167D6b35e51df22f42c4F42f26d365756D244fDE + signatures[0] = signWithPK(0xACC1, signingHash); // 0x2337f702bc9A7D1f415050365634FEbEdf4054Be + signatures[1] = signWithPK(0xACC2, signingHash); // 0x167D6b35e51df22f42c4F42f26d365756D244fDE + signatures[2] = signWithPK(0xACC3, signingHash); // 0x167D6b35e51df22f42c4F42f26d365756D244fDE bytes32[] memory roles = new bytes32[](3); roles[0] = LibHelpers._stringToBytes32(LC.ROLE_UNDERWRITER); @@ -389,9 +389,9 @@ contract T04EntityTest is D03ProtocolDefaults { bytes32 signingHash = nayms.getSigningHash(simplePolicy.startDate, simplePolicy.maturationDate, simplePolicy.asset, simplePolicy.limit, testPolicyDataHash); bytes[] memory signatures = new bytes[](3); - signatures[0] = initPolicySig(0xACC2, signingHash); - signatures[1] = initPolicySig(0xACC1, signingHash); - signatures[2] = initPolicySig(0xACC3, signingHash); + signatures[0] = signWithPK(0xACC2, signingHash); + signatures[1] = signWithPK(0xACC1, signingHash); + signatures[2] = signWithPK(0xACC3, signingHash); bytes32[] memory roles = new bytes32[](3); roles[0] = LibHelpers._stringToBytes32(LC.ROLE_UNDERWRITER); @@ -438,10 +438,10 @@ contract T04EntityTest is D03ProtocolDefaults { bytes32 signingHash = nayms.getSigningHash(simplePolicy.startDate, simplePolicy.maturationDate, simplePolicy.asset, simplePolicy.limit, testPolicyDataHash); - stakeholders.signatures[0] = initPolicySig(0xACC2, signingHash); - stakeholders.signatures[1] = initPolicySig(0xACC1, signingHash); - stakeholders.signatures[2] = initPolicySig(0xACC3, signingHash); - stakeholders.signatures[3] = initPolicySig(0xACC4, signingHash); + stakeholders.signatures[0] = signWithPK(0xACC2, signingHash); + stakeholders.signatures[1] = signWithPK(0xACC1, signingHash); + stakeholders.signatures[2] = signWithPK(0xACC3, signingHash); + stakeholders.signatures[3] = signWithPK(0xACC4, signingHash); // external token not supported vm.expectRevert("external token is not supported"); @@ -1165,139 +1165,106 @@ contract T04EntityTest is D03ProtocolDefaults { } function testSelfOnboardingNotApproved() public { - vm.stopPrank(); - vm.startPrank(signer1); - vm.expectRevert(abi.encodeWithSelector(EntityOnboardingNotApproved.selector, signer1)); - nayms.onboard(); - vm.stopPrank(); - } - - function testSelfOnboardingInvalidGroup() public { - bytes32 roleId = LibHelpers._stringToBytes32(LC.ROLE_SYSTEM_MANAGER); + bytes32 roleId = LibHelpers._stringToBytes32(LC.ROLE_ENTITY_CP); nayms.assignRole(em.id, systemContext, LC.ROLE_ONBOARDING_APPROVER); - vm.startPrank(em.addr); - vm.expectRevert(abi.encodeWithSelector(InvalidSelfOnboardRoleApproval.selector, roleId)); - nayms.approveSelfOnboarding(address(111), randomEntityId(1), roleId); - vm.stopPrank(); - } + bytes32 entityId = randomEntityId(1); + address userAddress = address(111); + bytes memory noSig; - function testSelfOnboardingAlreadyApproved() public { - nayms.assignRole(em.id, systemContext, LC.ROLE_ONBOARDING_APPROVER); + vm.startPrank(userAddress); + vm.expectRevert(abi.encodeWithSelector(EntityOnboardingNotApproved.selector, userAddress)); + nayms.onboardViaSignature(OnboardingApproval({ entityId: entityId, roleId: roleId, signature: noSig })); - bytes32 entityId = randomEntityId(2); - bytes32 roleId = LibHelpers._stringToBytes32(LC.ROLE_ENTITY_TOKEN_HOLDER); - vm.startPrank(em.addr); - nayms.approveSelfOnboarding(address(111), entityId, roleId); + bytes memory sig = signWithPK(em.pk, nayms.getOnboardingHash(userAddress, entityId, roleId)); + + vm.expectRevert(abi.encodeWithSelector(EntityOnboardingNotApproved.selector, userAddress)); + nayms.onboardViaSignature(OnboardingApproval({ entityId: 0x0, roleId: roleId, signature: sig })); + + vm.expectRevert(abi.encodeWithSelector(EntityOnboardingNotApproved.selector, userAddress)); + nayms.onboardViaSignature(OnboardingApproval({ entityId: entityId, roleId: 0x0, signature: sig })); + vm.stopPrank(); - vm.expectRevert(abi.encodeWithSelector(EntityOnboardingAlreadyApproved.selector, address(111))); - nayms.approveSelfOnboarding(address(111), entityId, roleId); + vm.startPrank(sm.addr); + nayms.assignRole(em.id, systemContext, LC.ROLE_ENTITY_CP); // remove onboarding approver role vm.stopPrank(); + + vm.startPrank(userAddress); + vm.expectRevert(abi.encodeWithSelector(EntityOnboardingNotApproved.selector, userAddress)); + nayms.onboardViaSignature(OnboardingApproval({ entityId: entityId, roleId: roleId, signature: sig })); } - function testSelfOnboardingSuccess() public { + function testSelfOnboardingInvalidGroup() public { + bytes32 sysMgrRoleId = LibHelpers._stringToBytes32(LC.ROLE_SYSTEM_MANAGER); nayms.assignRole(em.id, systemContext, LC.ROLE_ONBOARDING_APPROVER); - bytes32 roleId = LibHelpers._stringToBytes32(LC.ROLE_ENTITY_TOKEN_HOLDER); - bytes32 e1 = randomEntityId(1); - _approveSelfOnboarding(address(111), e1, roleId); - _selfOnboard(address(111), e1, LC.GROUP_TOKEN_HOLDERS); + bytes32 entityId = randomEntityId(1); + address userAddress = address(111); - bytes32 roleId2 = LibHelpers._stringToBytes32(LC.ROLE_ENTITY_CP); - bytes32 e2 = randomEntityId(2); - _approveSelfOnboarding(address(222), e2, roleId2); - _selfOnboard(address(222), e2, LC.GROUP_CAPITAL_PROVIDERS); + bytes memory sig = signWithPK(em.pk, nayms.getOnboardingHash(userAddress, entityId, sysMgrRoleId)); + + vm.startPrank(userAddress); + vm.expectRevert(abi.encodeWithSelector(InvalidSelfOnboardRoleApproval.selector, sysMgrRoleId)); + nayms.onboardViaSignature(OnboardingApproval({ entityId: entityId, roleId: sysMgrRoleId, signature: sig })); + vm.stopPrank(); } - function testSelfOnboardingUpgradeToCapitalProvider() public { + function testSelfOnboardingSuccess() public { + bytes32 roleId = LibHelpers._stringToBytes32(LC.ROLE_ENTITY_CP); nayms.assignRole(em.id, systemContext, LC.ROLE_ONBOARDING_APPROVER); - bytes32 roleIdTokenHolder = LibHelpers._stringToBytes32(LC.ROLE_ENTITY_TOKEN_HOLDER); - bytes32 roleIdCP = LibHelpers._stringToBytes32(LC.ROLE_ENTITY_CP); - // test upgrade before onboarding - bytes32 e1 = randomEntityId(1); - _approveSelfOnboarding(address(111), e1, roleIdTokenHolder); - _approveSelfOnboarding(address(111), e1, roleIdCP); - _selfOnboard(address(111), e1, LC.GROUP_CAPITAL_PROVIDERS); + bytes32 entityId = randomEntityId(1); + address userAddress = address(111); - // test upgrade after onboarding - bytes32 e2 = randomEntityId(2); - _approveSelfOnboarding(address(222), e2, roleIdTokenHolder); - _selfOnboard(address(222), e2, LC.GROUP_TOKEN_HOLDERS); - _approveSelfOnboarding(address(222), e2, roleIdCP); + bytes memory sig = signWithPK(em.pk, nayms.getOnboardingHash(userAddress, entityId, roleId)); - vm.recordLogs(); + vm.startPrank(userAddress); + nayms.onboardViaSignature(OnboardingApproval({ entityId: entityId, roleId: roleId, signature: sig })); + vm.stopPrank(); - _selfOnboard(address(222), e2, LC.GROUP_CAPITAL_PROVIDERS); + assertEq(nayms.getEntity(LibHelpers._getIdForAddress(userAddress)), entityId, "parent should be set"); - Vm.Log[] memory entries = vm.getRecordedLogs(); - assertEq(entries[0].topics.length, 2); - assertEq(entries[0].topics[0], keccak256("RoleUpdated(bytes32,bytes32,bytes32,string)")); - assertEq(entries[0].topics[1], e2); - (bytes32 contextId, bytes32 roleId, string memory action) = abi.decode(entries[0].data, (bytes32, bytes32, string)); - assertEq(contextId, e2); - assertEq(roleId, roleIdTokenHolder); - assertEq(action, "_unassignRole"); - - assertEq(entries[1].topics.length, 2); - assertEq(entries[1].topics[0], keccak256("RoleUpdated(bytes32,bytes32,bytes32,string)")); - assertEq(entries[1].topics[1], e2, "object ID doesn't match"); - (bytes32 contextId2, bytes32 roleId2, string memory action2) = abi.decode(entries[1].data, (bytes32, bytes32, string)); - assertEq(contextId2, systemContext, "incorrect context"); - assertEq(roleId2, roleIdTokenHolder, "wrong role"); - assertEq(action2, "_unassignRole", "wrong operation"); + assertTrue(nayms.isInGroup(entityId, systemContext, LC.GROUP_CAPITAL_PROVIDERS), "should belong capital providers group"); } - function testSelfOnboardingCancel() public { + function testSelfOnboardingUpgradeToCapitalProvider() public { nayms.assignRole(em.id, systemContext, LC.ROLE_ONBOARDING_APPROVER); - bytes32 entityId = randomEntityId(2); + address userAddress = address(111); bytes32 roleIdTokenHolder = LibHelpers._stringToBytes32(LC.ROLE_ENTITY_TOKEN_HOLDER); + bytes32 roleIdCapitalProvider = LibHelpers._stringToBytes32(LC.ROLE_ENTITY_CP); - vm.startPrank(em.addr); - nayms.approveSelfOnboarding(address(111), entityId, roleIdTokenHolder); - assertTrue(nayms.isSelfOnboardingApproved(address(111), entityId, roleIdTokenHolder), "Onboarding should be approved"); + bytes32 e1 = randomEntityId(1); - vm.expectRevert(abi.encodeWithSelector(InvalidGroupPrivilege.selector, em.addr._getIdForAddress(), systemContext, LC.ROLE_ONBOARDING_APPROVER, LC.GROUP_SYSTEM_MANAGERS)); - nayms.cancelSelfOnboarding(address(111)); - vm.stopPrank(); - vm.startPrank(sm.addr); - nayms.cancelSelfOnboarding(address(111)); + bytes memory sigTokenHolder = signWithPK(em.pk, nayms.getOnboardingHash(userAddress, e1, roleIdTokenHolder)); + bytes memory sigCapitalProvider = signWithPK(em.pk, nayms.getOnboardingHash(userAddress, e1, roleIdCapitalProvider)); - assertFalse(nayms.isSelfOnboardingApproved(address(111), entityId, roleIdTokenHolder), "Onboarding should have been cancelled"); - } + vm.startPrank(userAddress); + nayms.onboardViaSignature(OnboardingApproval({ entityId: e1, roleId: roleIdTokenHolder, signature: sigTokenHolder })); - function _approveSelfOnboarding(address _userAddress, bytes32 entityId, bytes32 roleId) private { vm.recordLogs(); - vm.startPrank(em.addr); - nayms.approveSelfOnboarding(_userAddress, entityId, roleId); - vm.stopPrank(); + nayms.onboardViaSignature(OnboardingApproval({ entityId: e1, roleId: roleIdCapitalProvider, signature: sigCapitalProvider })); Vm.Log[] memory entries = vm.getRecordedLogs(); - assertEq(entries[0].topics.length, 2); - assertEq(entries[0].topics[0], keccak256("SelfOnboardingApproved(address)")); - assertEq(abi.decode(LibHelpers._bytes32ToBytes(entries[0].topics[1]), (address)), _userAddress); - } - function _selfOnboard(address _userAddress, bytes32 entityId, string memory groupName) private { - vm.startPrank(_userAddress); - nayms.onboard(); - vm.stopPrank(); - - assertTrue(nayms.isInGroup(entityId, systemContext, groupName)); - assertTrue(nayms.isInGroup(entityId, entityId, groupName)); + assertRoleUpdateEvent(entries, 0, systemContext, e1, roleIdTokenHolder, "_unassignRole"); + assertRoleUpdateEvent(entries, 1, systemContext, e1, roleIdCapitalProvider, "_assignRole"); } - function test_ApproveSelfOnboarding_InvalidEntityId() public { + function test_selfOnboarding_InvalidEntityId() public { + bytes32 roleId = LibHelpers._stringToBytes32(LC.ROLE_ENTITY_CP); nayms.assignRole(em.id, systemContext, LC.ROLE_ONBOARDING_APPROVER); bytes32 entityId = keccak256("invalid entity id"); - bytes32 roleId = LibHelpers._stringToBytes32(LC.ROLE_ENTITY_TOKEN_HOLDER); + address userAddress = address(111); - vm.startPrank(em.addr); - vm.expectRevert(abi.encodeWithSelector(InvalidEntityId.selector, entityId)); - nayms.approveSelfOnboarding(address(111), entityId, roleId); + bytes memory sig = signWithPK(em.pk, nayms.getOnboardingHash(userAddress, entityId, roleId)); + + vm.startPrank(userAddress); + vm.expectRevert(abi.encodeWithSelector(InvalidObjectType.selector, entityId, LC.OBJECT_TYPE_ENTITY)); + nayms.onboardViaSignature(OnboardingApproval({ entityId: entityId, roleId: roleId, signature: sig })); + vm.stopPrank(); } function randomEntityId(uint256 salt) public view returns (bytes32) { diff --git a/test/T04Market.t.sol b/test/T04Market.t.sol index f4f69793..26471a01 100644 --- a/test/T04Market.t.sol +++ b/test/T04Market.t.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.20; -import { D03ProtocolDefaults, LibHelpers, LibObject, LC, c } from "./defaults/D03ProtocolDefaults.sol"; +import { D03ProtocolDefaults, LibHelpers, LC } from "./defaults/D03ProtocolDefaults.sol"; import { Vm } from "forge-std/Vm.sol"; import { StdStyle } from "forge-std/Test.sol"; import { MockAccounts } from "./utils/users/MockAccounts.sol"; -import { Entity, MarketInfo, FeeSchedule, SimplePolicy, Stakeholders, CalculatedFees } from "src/shared/FreeStructs.sol"; +import { Entity, MarketInfo, SimplePolicy, Stakeholders } from "src/shared/FreeStructs.sol"; import { StdStyle } from "forge-std/StdStyle.sol"; @@ -650,6 +650,86 @@ contract T04MarketTest is D03ProtocolDefaults, MockAccounts { // logOfferDetails(4); // should be filled 50% } + function testOrderMatchedEventsForSecondaryTradeWithBetterThanAskPrice() public { + uint256 tokenAmount = 1000 ether; + + writeTokenBalance(account0, naymsAddress, wethAddress, dt.entity1StartingBal); + + changePrank(sm.addr); + nayms.assignRole(signer2Id, systemContext, LC.ROLE_ENTITY_CP); + + // 1. Start token sale + nayms.createEntity(entity1, signer1Id, initEntity(wethId, collateralRatio_500, maxCapital_2000eth, true), "test"); + nayms.enableEntityTokenization(entity1, "E1PT", "E1-P-Token", 1e13); + nayms.startTokenSale(entity1, tokenAmount, tokenAmount); // SELL: PT 1000 / ETH 1000 (price = 1) + + // 2. Purchase P-Tokens + nayms.createEntity(entity2, signer2Id, initEntity(wethId, collateralRatio_500, maxCapital_2000eth, true), "test"); + changePrank(signer2); + writeTokenBalance(signer2, naymsAddress, wethAddress, dt.entity2ExternalDepositAmt * 6); + nayms.externalDeposit(wethAddress, dt.entity2ExternalDepositAmt * 6); + + // BUY: P 500 / E 500 (price = 1) + nayms.executeLimitOffer(wethId, tokenAmount / 2, entity1, tokenAmount / 2); + + // SELL: P 500 / E 2000 (price = 4) + nayms.executeLimitOffer(entity1, tokenAmount / 2, wethId, tokenAmount * 2); + + // 3. BUY P 1000 + changePrank(sm.addr); + nayms.createEntity(entity3, signer3Id, initEntity(wethId, collateralRatio_500, maxCapital_2000eth, true), "test"); + + changePrank(signer3); + writeTokenBalance(signer3, naymsAddress, wethAddress, dt.entity2ExternalDepositAmt * 6); + nayms.externalDeposit(wethAddress, dt.entity2ExternalDepositAmt * 6); + + vm.recordLogs(); + nayms.executeLimitOffer(wethId, tokenAmount * 4, entity1, tokenAmount); + + assertOfferFilled(1, entity1, entity1, tokenAmount, wethId, tokenAmount); + assertOfferFilled(2, entity2, wethId, tokenAmount / 2, entity1, tokenAmount / 2); + assertOfferFilled(3, entity2, entity1, tokenAmount / 2, wethId, tokenAmount * 2); + assertOfferFilled(4, entity3, wethId, tokenAmount * 4, entity1, tokenAmount); + + // assert OrderMatched events ONLY for the last trade to verify match at a better-than-asked-for price + Vm.Log[] memory entries = vm.getRecordedLogs(); + + assertOrderMatchedEvent(entries, 11, 4, 1, tokenAmount / 2, tokenAmount / 2); + assertOrderMatchedEvent(entries, 12, 1, 4, tokenAmount / 2, tokenAmount / 2); + assertOrderMatchedEvent(entries, 21, 4, 3, tokenAmount * 2, tokenAmount / 2); + assertOrderMatchedEvent(entries, 22, 3, 4, tokenAmount / 2, tokenAmount * 2); + + // logOfferDetails(1); // should be filled 100% + // logOfferDetails(2); // should be filled 100% + // logOfferDetails(3); // should be filled 100% + // logOfferDetails(4); // should be filled 100% + } + + function assertOrderMatchedEvent( + Vm.Log[] memory _entries, + uint256 _entryIndex, + uint256 _orderId, + uint256 _matchedWithId, + uint256 _sellAmountMatched, + uint256 _buyAmountMatched + ) private { + assertEq(_entries[_entryIndex].topics.length, 2, string.concat("OrderMatched[", vm.toString(_orderId), "]: topics length incorrect")); + assertEq( + _entries[_entryIndex].topics[0], + keccak256("OrderMatched(uint256,uint256,uint256,uint256)"), + string.concat("OrderMatched[", vm.toString(_orderId), "]: Invalid event signature") + ); + assertEq( + abi.decode(LibHelpers._bytes32ToBytes(_entries[_entryIndex].topics[1]), (uint256)), + _orderId, + string.concat("OrderMatched[", vm.toString(_orderId), "]: incorrect orderID") + ); // assert order ID + (uint256 matchedWithId, uint256 sellAmountMatched, uint256 buyAmountMatched) = abi.decode(_entries[_entryIndex].data, (uint256, uint256, uint256)); + assertEq(matchedWithId, _matchedWithId, string.concat("OrderMatched[", vm.toString(_orderId), "]: invalid matchedWithID")); + assertEq(sellAmountMatched, _sellAmountMatched, string.concat("OrderMatched[", vm.toString(_orderId), "]: invalid sell amount")); + assertEq(buyAmountMatched, _buyAmountMatched, string.concat("OrderMatched[", vm.toString(_orderId), "]: invalid buy amount")); + } + function testBestOffersWithCancel() public { testStartTokenSale(); @@ -932,7 +1012,6 @@ contract T04MarketTest is D03ProtocolDefaults, MockAccounts { uint256 prev1 = nayms.getOffer(bestId).rankPrev; uint256 prev2 = nayms.getOffer(prev1).rankPrev; - // c.log(" --------- ".red()); logOfferDetails(bestId); logOfferDetails(prev1); logOfferDetails(prev2); @@ -986,7 +1065,7 @@ contract T04MarketTest is D03ProtocolDefaults, MockAccounts { nayms.enableEntityTokenization(userA.entityId, "E1", "Entity 1 Token", 1e6); nayms.startTokenSale(userA.entityId, pToken100, usdc1000 * 2); - /// Attack script + /// Attack script: /// place order and lock funds vm.startPrank(attacker.addr); nayms.executeLimitOffer(usdcId, usdc1000, userA.entityId, pToken100); @@ -1008,29 +1087,33 @@ contract T04MarketTest is D03ProtocolDefaults, MockAccounts { vm.startPrank(sm.addr); nayms.setMinimumSell(usdcId, 1e6); assertEq(nayms.objectMinimumSell(usdcId), 1e6, "unexpected minimum sell amount"); + bytes32 e1Id = createTestEntity(ea.id); ea.entityId = e1Id; nayms.enableEntityTokenization(e1Id, "E1", "Entity 1", 1e12); hSetEntity(tcp, e1Id); + // Selling 10 pTokens for 1_000_000 USDC nayms.startTokenSale(e1Id, 10e18, 1_000_000e6); - hAssignRole(tcp.id, e1Id, LC.ROLE_ENTITY_CP); - fundEntityUsdc(ea, 1_000_000e6); - // If the amount being sold is less than the minimum sell amount, the offer is expected to go into the - // "fulfilled" state + + // If the amount being sold is less than the minimum sell amount, the offer is expected to go into the "fulfilled" state vm.startPrank(tcp.addr); + (uint256 lastOfferId, , ) = nayms.executeLimitOffer(usdcId, 1e6 - 1, e1Id, 10e18); MarketInfo memory m = logOfferDetails(lastOfferId); assertEq(m.state, LC.OFFER_STATE_FULFILLED, "unexpected offer state"); + (lastOfferId, , ) = nayms.executeLimitOffer(usdcId, 1e6, e1Id, 1e12 + 1); m = logOfferDetails(lastOfferId); assertEq(m.state, LC.OFFER_STATE_ACTIVE, "unexpected offer state"); + (lastOfferId, , ) = nayms.executeLimitOffer(usdcId, 1e6 + 1, e1Id, 1e12); m = logOfferDetails(lastOfferId); assertEq(m.state, LC.OFFER_STATE_ACTIVE, "unexpected offer state"); + (lastOfferId, , ) = nayms.executeLimitOffer(usdcId, 1e6, e1Id, 1e12 - 1); m = logOfferDetails(lastOfferId); assertEq(m.state, LC.OFFER_STATE_FULFILLED, "unexpected offer state"); diff --git a/test/T05Fees.t.sol b/test/T05Fees.t.sol index 277feaac..80ecad9d 100644 --- a/test/T05Fees.t.sol +++ b/test/T05Fees.t.sol @@ -7,7 +7,7 @@ import { StdStyle } from "forge-std/Test.sol"; import { D03ProtocolDefaults, LC } from "./defaults/D03ProtocolDefaults.sol"; import { Entity, FeeSchedule, CalculatedFees } from "../src/shared/AppStorage.sol"; -import { SimplePolicy, SimplePolicyInfo, Stakeholders } from "../src/shared/FreeStructs.sol"; +import { SimplePolicy, Stakeholders } from "../src/shared/FreeStructs.sol"; import "src/shared/CustomErrors.sol"; import { LibHelpers } from "src/libs/LibHelpers.sol"; diff --git a/test/T05TokenWrapper.t.sol b/test/T05TokenWrapper.t.sol index 1ae338ea..ea490f1d 100644 --- a/test/T05TokenWrapper.t.sol +++ b/test/T05TokenWrapper.t.sol @@ -4,7 +4,6 @@ pragma solidity 0.8.20; import { Vm } from "forge-std/Vm.sol"; import { D03ProtocolDefaults, c, LC } from "./defaults/D03ProtocolDefaults.sol"; -import { Entity } from "src/shared/FreeStructs.sol"; import { ERC20Wrapper } from "../src/utils/ERC20Wrapper.sol"; contract T05TokenWrapper is D03ProtocolDefaults { diff --git a/test/T06Staking.t.sol b/test/T06Staking.t.sol index 152754e2..049b8a3d 100644 --- a/test/T06Staking.t.sol +++ b/test/T06Staking.t.sol @@ -1,15 +1,13 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.20; -import { StdStorage, stdStorage, StdStyle, StdAssertions } from "forge-std/Test.sol"; -import { Vm } from "forge-std/Vm.sol"; +import { StdStorage, stdStorage, StdStyle } from "forge-std/Test.sol"; import { D03ProtocolDefaults, c, LC, LibHelpers } from "./defaults/D03ProtocolDefaults.sol"; -import { StakingConfig, StakingState } from "src/shared/FreeStructs.sol"; +import { StakingConfig } from "src/shared/FreeStructs.sol"; import { IDiamondCut } from "lib/diamond-2-hardhat/contracts/interfaces/IDiamondCut.sol"; import { StakingFixture } from "test/fixtures/StakingFixture.sol"; import { DummyToken } from "./utils/DummyToken.sol"; import { LibTokenizedVaultStaking } from "src/libs/LibTokenizedVaultStaking.sol"; -import { IERC20 } from "src/interfaces/IERC20.sol"; import { IntervalRewardPayedOutAlready, InvalidTokenRewardAmount, InvalidStakingAmount, InvalidStaker, EntityDoesNotExist, StakingAlreadyStarted, StakingNotStarted, StakingConfigDoesNotExist } from "src/shared/CustomErrors.sol"; @@ -27,17 +25,18 @@ contract T06Staking is D03ProtocolDefaults { uint64 private constant R = (85 * SCALE_FACTOR) / 100; uint64 private constant I = 30 days; - bytes32 immutable VTOKENID = makeId2(LC.OBJECT_TYPE_ENTITY, bytes20(keccak256(bytes("test")))); + bytes32 internal immutable VTOKENID = makeId2(LC.OBJECT_TYPE_ENTITY, bytes20(keccak256(bytes("test")))); - bytes32 VTOKENID1; - bytes32 NAYM_ID; + bytes32 internal VTOKENID1; + bytes32 internal NAYM_ID; - NaymsAccount bob; - NaymsAccount sue; - NaymsAccount lou; - NaymsAccount nlf; - DummyToken naymToken; + NaymsAccount internal bob; + NaymsAccount internal sue; + NaymsAccount internal lou; + + NaymsAccount internal nlf; + DummyToken internal naymToken; uint256 private constant usdcTotal = 1_000_000e6; uint256 private constant wethTotal = 1_000_000e18; @@ -48,18 +47,18 @@ contract T06Staking is D03ProtocolDefaults { uint256 private constant totalStakeAmount = bobStakeAmount + sueStakeAmount + louStakeAmount; - uint256 immutable rewardAmount = 100e6; + uint256 internal immutable rewardAmount = 100e6; - uint256 constant stakingStart = 100 days; + uint256 internal constant stakingStart = 100 days; StakingFixture internal stakingFixture; - mapping(bytes32 entityId => uint256) usdcBalance; - mapping(bytes32 entityId => mapping(uint64 index => uint256)) unclaimedReward; + mapping(bytes32 entityId => uint256) internal usdcBalance; + mapping(bytes32 entityId => mapping(uint64 index => uint256)) internal unclaimedReward; - uint256 bobCurrentReward; - uint256 sueCurrentReward; - uint256 louCurrentReward; + uint256 internal bobCurrentReward; + uint256 internal sueCurrentReward; + uint256 internal louCurrentReward; function setUp() public { stakingFixture = new StakingFixture(); @@ -759,6 +758,48 @@ contract T06Staking is D03ProtocolDefaults { assertEq(nayms.internalBalanceOf(bob.entityId, usdcId), rewardAmount * 2); } + function test_fuzzCompoundReward(uint256 testReward) public { + vm.assume(100 < testReward && testReward < type(uint128).max); + + naymToken.mint(nlf.addr, testReward); + vm.startPrank(nlf.addr); + naymToken.approve(address(nayms), testReward); + nayms.externalDeposit(address(naymToken), testReward); + + uint256 startStaking = block.timestamp + 1; + initStaking(startStaking); + + vm.warp(startStaking + 31 days); + + startPrank(bob); + nayms.stake(nlf.entityId, bobStakeAmount); + assertStakedAmount(bob.entityId, bobStakeAmount, "Bob's stake should increase"); + + vm.warp(startStaking + 61 days); + + assertEq(nayms.lastPaidInterval(nlf.entityId), 0, "Last interval paid should be 0"); + + startPrank(nlf); + nayms.payReward(makeId(LC.OBJECT_TYPE_STAKING_REWARD, bytes20("reward1")), nlf.entityId, usdcId, 10_000); + assertEq(nayms.lastPaidInterval(nlf.entityId), 2, "Last interval paid should be 0"); + + vm.warp(startStaking + 91 days); + + startPrank(bob); + vm.expectRevert("No reward to compound"); + nayms.compoundRewards(nlf.entityId); + + startPrank(nlf); + nayms.payReward(makeId(LC.OBJECT_TYPE_STAKING_REWARD, bytes20("reward2")), nlf.entityId, NAYM_ID, testReward); + assertEq(nayms.lastPaidInterval(nlf.entityId), 3, "Last interval paid should be 0"); + + vm.warp(startStaking + 181 days); + + startPrank(bob); + nayms.compoundRewards(nlf.entityId); + assertStakedAmount(bob.entityId, bobStakeAmount + testReward, "Bob's stake should increase"); + } + function test_twoStakingRewardCurrencies() public { uint256 startStaking = block.timestamp + 100 days; initStaking(startStaking); diff --git a/test/T07Zaps.t.sol b/test/T07Zaps.t.sol new file mode 100644 index 00000000..fd2883a4 --- /dev/null +++ b/test/T07Zaps.t.sol @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +import { D03ProtocolDefaults, LC, LibHelpers, StdStyle } from "./defaults/D03ProtocolDefaults.sol"; +import { DummyToken } from "test/utils/DummyToken.sol"; +import { StakingConfig, PermitSignature, OnboardingApproval } from "src/shared/FreeStructs.sol"; +import { InvalidERC20Token } from "src/shared/CustomErrors.sol"; + +import { InvalidSignatureLength } from "src/shared/CustomErrors.sol"; + +contract ZapFacetTest is D03ProtocolDefaults { + using LibHelpers for address; + using StdStyle for *; + + DummyToken internal naymToken = new DummyToken(); + DummyToken internal rewardToken; + + NaymsAccount internal bob = makeNaymsAcc("Bob"); + NaymsAccount internal sue = makeNaymsAcc("Sue"); + + NaymsAccount internal nlf = makeNaymsAcc(LC.NLF_IDENTIFIER); + + uint64 private constant SCALE_FACTOR = 1_000_000; // 6 digits because USDC + uint64 private constant A = (15 * SCALE_FACTOR) / 100; + uint64 private constant R = (85 * SCALE_FACTOR) / 100; + uint64 private constant I = 30 days; + bytes32 internal NAYM_ID = address(naymToken)._getIdForAddress(); + function initStaking(uint256 initDate) internal { + StakingConfig memory config = StakingConfig({ + tokenId: NAYM_ID, + initDate: initDate, + a: A, // Amplification factor + r: R, // Boost decay factor + divider: SCALE_FACTOR, + interval: I // Amount of time per interval in seconds + }); + + startPrank(sa); + nayms.initStaking(nlf.entityId, config); + vm.stopPrank(); + } + + uint256 internal stakeAmount = 1e18; + uint256 internal unstakeAmount = 1e18; + + function setUp() public { + naymToken.mint(bob.addr, stakeAmount); + + startPrank(sa); + nayms.addSupportedExternalToken(address(naymToken), 100); + + vm.startPrank(sm.addr); + hCreateEntity(bob.entityId, bob, entity, "Bob data"); + hCreateEntity(nlf.entityId, nlf, entity, "NLF"); + } + + function test_zapStake_Success() public { + initStaking(block.timestamp + 1 + 7 days); + + // Prepare permit data + uint256 deadline = block.timestamp; + + // Create permit digest + bytes32 digest = keccak256( + abi.encodePacked( + "\x19\x01", + naymToken.DOMAIN_SEPARATOR(), + keccak256(abi.encode(naymToken.PERMIT_TYPEHASH(), bob.addr, address(nayms), stakeAmount, naymToken.nonces(owner), deadline)) + ) + ); + + // Sign the digest + (uint8 v, bytes32 r, bytes32 s) = vm.sign(bob.pk, digest); + + startPrank(bob); + + PermitSignature memory permitSignature = PermitSignature({ deadline: deadline, v: v, r: r, s: s }); + OnboardingApproval memory approval; + + vm.expectRevert(abi.encodeWithSelector(InvalidERC20Token.selector, address(111), "zapStake")); + nayms.zapStake(address(111), nlf.entityId, stakeAmount, stakeAmount, permitSignature, approval); + + nayms.zapStake(address(naymToken), nlf.entityId, stakeAmount, stakeAmount, permitSignature, approval); + + (uint256 staked, ) = nayms.getStakingAmounts(bob.entityId, nlf.entityId); + + assertEq(stakeAmount, staked, "bob's stake amount should increase"); + } + + function test_zapOrder_Success() public { + changePrank(sm.addr); + nayms.assignRole(em.id, systemContext, LC.ROLE_ONBOARDING_APPROVER); + + nayms.enableEntityTokenization(bob.entityId, "e1token", "e1token", 1e6); + + // Selling bob p tokens for weth + nayms.startTokenSale(bob.entityId, 1 ether, 1 ether); + + deal(address(weth), sue.addr, 10 ether); + + // Prepare permit data + uint256 deadline = block.timestamp; + bytes32 PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); + uint256 nonce = weth.nonces(sue.addr); + bytes32 structHash = keccak256(abi.encode(PERMIT_TYPEHASH, sue.addr, address(nayms), 10 ether, nonce, deadline)); + bytes32 digest = keccak256(abi.encodePacked("\x19\x01", weth.DOMAIN_SEPARATOR(), structHash)); + + bytes32 cpRoleId = LibHelpers._stringToBytes32(LC.ROLE_CAPITAL_PROVIDER); + bytes memory sig = signWithPK(em.pk, nayms.getOnboardingHash(sue.addr, sue.entityId, cpRoleId)); + + startPrank(sue); + + // Sign the digest + (uint8 v, bytes32 r, bytes32 s) = vm.sign(sue.pk, digest); + PermitSignature memory permitSignature = PermitSignature({ deadline: deadline, v: v, r: r, s: s }); + + // Sign onboarding approval + OnboardingApproval memory onboardingApproval = OnboardingApproval({ entityId: sue.entityId, roleId: cpRoleId, signature: sig }); + + // verify token address + vm.expectRevert(abi.encodeWithSelector(InvalidERC20Token.selector, address(111), "zapOrder")); + nayms.zapOrder(address(111), 10 ether, wethId, 1 ether, bob.entityId, 1 ether, permitSignature, onboardingApproval); + + OnboardingApproval memory oa = OnboardingApproval ({ + entityId: onboardingApproval.entityId, + roleId: onboardingApproval.roleId, + signature: abi.encode(uint16(10000), onboardingApproval.entityId, onboardingApproval.roleId) + }); + + vm.expectRevert(abi.encodePacked(InvalidSignatureLength.selector)); + nayms.zapOrder(address(weth), 10 ether, wethId, 1 ether, bob.entityId, 1 ether, permitSignature, oa); + + // Caller should ensure they deposit enough to cover order fees. + nayms.zapOrder(address(weth), 10 ether, wethId, 1 ether, bob.entityId, 1 ether, permitSignature, onboardingApproval); + + assertEq(nayms.internalBalanceOf(sue.entityId, bob.entityId), 1 ether, "sue should've purchased 1e18 bob p tokens"); + } +} diff --git a/test/defaults/D01Deployment.sol b/test/defaults/D01Deployment.sol index 03f480ed..6fcf6923 100644 --- a/test/defaults/D01Deployment.sol +++ b/test/defaults/D01Deployment.sol @@ -53,6 +53,7 @@ abstract contract D01Deployment is D00GlobalDefaults, Test { function makeNaymsAcc(string memory name) public returns (NaymsAccount memory) { (address addr, uint256 privateKey) = makeAddrAndKey(name); + vm.label(addr, name); return NaymsAccount({ id: LibHelpers._getIdForAddress(addr), entityId: makeId(LC.OBJECT_TYPE_ENTITY, bytes20(keccak256(bytes(name)))), pk: privateKey, addr: addr }); } diff --git a/test/defaults/D03ProtocolDefaults.sol b/test/defaults/D03ProtocolDefaults.sol index 3537dc62..df9ca31d 100644 --- a/test/defaults/D03ProtocolDefaults.sol +++ b/test/defaults/D03ProtocolDefaults.sol @@ -1,13 +1,13 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.20; +import { Vm } from "forge-std/Vm.sol"; + import { D02TestSetup, LibHelpers, c } from "./D02TestSetup.sol"; import { Entity, SimplePolicy, MarketInfo, Stakeholders, FeeSchedule } from "src/shared/FreeStructs.sol"; -import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import { MessageHashUtils } from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import { LibAdmin } from "src/libs/LibAdmin.sol"; -import { LibObject } from "src/libs/LibObject.sol"; import { LibConstants as LC } from "src/libs/LibConstants.sol"; import { StdStyle } from "forge-std/StdStyle.sol"; @@ -15,6 +15,7 @@ import { IERC20 } from "src/interfaces/IERC20.sol"; // solhint-disable no-console // solhint-disable state-visibility +// solhint-disable max-states-count /// @notice Default test setup part 03 /// Protocol / project level defaults @@ -440,16 +441,16 @@ contract D03ProtocolDefaults is D02TestSetup { bytes[] memory signatures = new bytes[](4); bytes32 signingHash = nayms.getSigningHash(policy.startDate, policy.maturationDate, policy.asset, policy.limit, offchainDataHash); - signatures[0] = initPolicySig(0xACC2, signingHash); - signatures[1] = initPolicySig(0xACC1, signingHash); - signatures[2] = initPolicySig(0xACC3, signingHash); - signatures[3] = initPolicySig(0xACC4, signingHash); + signatures[0] = signWithPK(0xACC2, signingHash); + signatures[1] = signWithPK(0xACC1, signingHash); + signatures[2] = signWithPK(0xACC3, signingHash); + signatures[3] = signWithPK(0xACC4, signingHash); policyStakeholders = Stakeholders(roles, entityIds, signatures); } } - function initPolicySig(uint256 privateKey, bytes32 signingHash) internal pure returns (bytes memory sig_) { + function signWithPK(uint256 privateKey, bytes32 signingHash) internal pure returns (bytes memory sig_) { (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, MessageHashUtils.toEthSignedMessageHash(signingHash)); sig_ = abi.encodePacked(r, s, v); } @@ -481,4 +482,15 @@ contract D03ProtocolDefaults is D02TestSetup { c.logBytes32(cr[i]); } } + + function assertRoleUpdateEvent(Vm.Log[] memory entries, uint256 _index, bytes32 _ctxId, bytes32 _entityId, bytes32 _roleId, string memory _action) internal { + assertEq(entries[_index].topics.length, 2); + assertEq(entries[_index].topics[0], keccak256("RoleUpdated(bytes32,bytes32,bytes32,string)")); + assertEq(entries[_index].topics[1], _entityId, "incorrect entityID".red()); + + (bytes32 contextId, bytes32 roleId, string memory action) = abi.decode(entries[_index].data, (bytes32, bytes32, string)); + assertEq(contextId, _ctxId, "incorrect context".red()); + assertEq(_roleId, roleId, "incorrect role ID".red()); + assertEq(action, _action, "incorrect action".red()); + } } diff --git a/test/fixtures/LibFeeRouterFixture.sol b/test/fixtures/LibFeeRouterFixture.sol index b04e63bf..ebd2e8c1 100644 --- a/test/fixtures/LibFeeRouterFixture.sol +++ b/test/fixtures/LibFeeRouterFixture.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.20; import { LibConstants } from "src/libs/LibConstants.sol"; -import { LibFeeRouter, CalculatedFees, FeeAllocation, FeeSchedule } from "src/libs/LibFeeRouter.sol"; +import { LibFeeRouter, CalculatedFees } from "src/libs/LibFeeRouter.sol"; /// Create a fixture to test the library LibFeeRouter diff --git a/test/fixtures/TokenizedVaultFixture.sol b/test/fixtures/TokenizedVaultFixture.sol index 51563f7f..936630e8 100644 --- a/test/fixtures/TokenizedVaultFixture.sol +++ b/test/fixtures/TokenizedVaultFixture.sol @@ -3,11 +3,13 @@ pragma solidity 0.8.20; import { LibTokenizedVaultIO } from "src/libs/LibTokenizedVaultIO.sol"; import { LibAdmin } from "src/libs/LibAdmin.sol"; +import { InvalidERC20Token } from "src/shared/CustomErrors.sol"; contract TokenizedVaultFixture { function externalDepositDirect(bytes32 _to, address _externalTokenAddress, uint256 _amount) public { - // a user can only deposit an approved external ERC20 token - require(LibAdmin._isSupportedExternalTokenAddress(_externalTokenAddress), "extDeposit: invalid ERC20 token"); + if (!LibAdmin._isSupportedExternalTokenAddress(_externalTokenAddress)) { + revert InvalidERC20Token(_externalTokenAddress, "extDeposit"); + } LibTokenizedVaultIO._externalDeposit(_to, _externalTokenAddress, _amount); } } diff --git a/test/utils/DummyToken.sol b/test/utils/DummyToken.sol index 921d18e0..d1e0de1f 100644 --- a/test/utils/DummyToken.sol +++ b/test/utils/DummyToken.sol @@ -2,8 +2,11 @@ pragma solidity 0.8.20; import { IERC20 } from "src/interfaces/IERC20.sol"; +import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; contract DummyToken is IERC20 { + using ECDSA for bytes32; + string public name = "Dummy"; string public symbol = "DUM"; uint8 public decimals = 18; @@ -11,38 +14,80 @@ contract DummyToken is IERC20 { mapping(address => uint256) public balanceOf; mapping(address => mapping(address => uint256)) public allowance; - function transfer(address to, uint256 value) external returns (bool) { - if (value == 0) { - return false; + // EIP-2612 permit state variables + bytes32 public DOMAIN_SEPARATOR; + // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); + bytes32 public constant PERMIT_TYPEHASH = 0x6d47c92dbe9aa29a8e9e38d25f3f54ab645e5df690ddf0d3e2a24ec2445a44f0; + mapping(address => uint256) public nonces; + + constructor() { + uint256 chainId; + assembly { + chainId := chainid() } + DOMAIN_SEPARATOR = keccak256( + abi.encode( + // keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)") + 0x8b73e7bb5ba7313e92d4a46294e43b9c1bafabf1adbe7b6f4bdfd44c38a7e6d4, + keccak256(bytes(name)), + keccak256(bytes("1")), + chainId, + address(this) + ) + ); + } + function transfer(address to, uint256 value) external returns (bool) { require(balanceOf[msg.sender] >= value, "not enough balance"); balanceOf[msg.sender] -= value; balanceOf[to] += value; + emit Transfer(msg.sender, to, value); return true; } function approve(address spender, uint256 value) external returns (bool) { allowance[msg.sender][spender] = value; + emit Approval(msg.sender, spender, value); return true; } function transferFrom(address from, address to, uint256 value) external returns (bool) { - if (value == 0) { - revert(); - } - require(allowance[from][msg.sender] >= value, "not enough allowance"); require(balanceOf[from] >= value, "not enough balance"); + allowance[from][msg.sender] -= value; balanceOf[from] -= value; balanceOf[to] += value; + emit Transfer(from, to, value); return true; } function mint(address to, uint256 value) external { balanceOf[to] += value; totalSupply += value; + emit Transfer(address(0), to, value); } - function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external {} + /** + * @notice Approves tokens via signature, as per EIP-2612 + * @param owner The token owner's address + * @param spender The spender's address + * @param value The amount to approve + * @param deadline The deadline timestamp by which the permit must be used + * @param v The recovery byte of the signature + * @param r Half of the ECDSA signature pair + * @param s Half of the ECDSA signature pair + */ + function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external override { + require(block.timestamp <= deadline, "permit: expired deadline"); + + bytes32 structHash = keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline)); + + bytes32 digest = keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, structHash)); + + address recoveredAddress = digest.recover(v, r, s); + require(recoveredAddress == owner, "permit: invalid signature"); + + allowance[owner][spender] = value; + emit Approval(owner, spender, value); + } }