ваше сообщение коммита
This commit is contained in:
5
-b
5
-b
@@ -1,5 +0,0 @@
|
||||
# Netscape HTTP Cookie File
|
||||
# https://curl.se/docs/http-cookies.html
|
||||
# This file was generated by libcurl! Edit at your own risk.
|
||||
|
||||
#HttpOnly_localhost FALSE / FALSE 1757248505 sessionId s%3ARIwffwSv4wqUqAqxpOk_ya9pUWlGlu4W.yc4Bi0rNrhM9%2BUzO0IUZezYTJJfB2ybrng1a3P0imjw
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../build-info/362ff3981c938c72363f6427a454b84b.json"
|
||||
"buildInfo": "../../build-info/998bc5520c3173891dd242df2365077b.json"
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
866
backend/cache/solidity-files-cache.json
vendored
866
backend/cache/solidity-files-cache.json
vendored
@@ -2,8 +2,8 @@
|
||||
"_format": "hh-sol-cache-2",
|
||||
"files": {
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/contracts/DLE.sol": {
|
||||
"lastModificationDate": 1754485037554,
|
||||
"contentHash": "f121cb518877db715ab5cd2e3ee5ff3a",
|
||||
"lastModificationDate": 1754682456067,
|
||||
"contentHash": "6d46f24614a8d6c838144dcfad200e26",
|
||||
"sourceName": "contracts/DLE.sol",
|
||||
"solcConfig": {
|
||||
"version": "0.8.20",
|
||||
@@ -32,8 +32,10 @@
|
||||
},
|
||||
"imports": [
|
||||
"@openzeppelin/contracts/token/ERC20/ERC20.sol",
|
||||
"@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol",
|
||||
"@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol",
|
||||
"@openzeppelin/contracts/utils/ReentrancyGuard.sol",
|
||||
"@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"
|
||||
"@openzeppelin/contracts/utils/cryptography/ECDSA.sol"
|
||||
],
|
||||
"versionPragmas": [
|
||||
"^0.8.20"
|
||||
@@ -348,6 +350,864 @@
|
||||
"artifacts": [
|
||||
"Hashes"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol": {
|
||||
"lastModificationDate": 1754306768254,
|
||||
"contentHash": "b1a8fc63b83ce00408e0c9ed1230b717",
|
||||
"sourceName": "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol",
|
||||
"solcConfig": {
|
||||
"version": "0.8.20",
|
||||
"settings": {
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 200
|
||||
},
|
||||
"viaIR": true,
|
||||
"evmVersion": "paris",
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
||||
"abi",
|
||||
"evm.bytecode",
|
||||
"evm.deployedBytecode",
|
||||
"evm.methodIdentifiers",
|
||||
"metadata"
|
||||
],
|
||||
"": [
|
||||
"ast"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"./IERC20Permit.sol",
|
||||
"../ERC20.sol",
|
||||
"../../../utils/cryptography/ECDSA.sol",
|
||||
"../../../utils/cryptography/EIP712.sol",
|
||||
"../../../utils/Nonces.sol"
|
||||
],
|
||||
"versionPragmas": [
|
||||
"^0.8.20"
|
||||
],
|
||||
"artifacts": [
|
||||
"ERC20Permit"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/utils/cryptography/ECDSA.sol": {
|
||||
"lastModificationDate": 1754306764456,
|
||||
"contentHash": "81de029d56aa803972be03c5d277cb6c",
|
||||
"sourceName": "@openzeppelin/contracts/utils/cryptography/ECDSA.sol",
|
||||
"solcConfig": {
|
||||
"version": "0.8.20",
|
||||
"settings": {
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 200
|
||||
},
|
||||
"viaIR": true,
|
||||
"evmVersion": "paris",
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
||||
"abi",
|
||||
"evm.bytecode",
|
||||
"evm.deployedBytecode",
|
||||
"evm.methodIdentifiers",
|
||||
"metadata"
|
||||
],
|
||||
"": [
|
||||
"ast"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"imports": [],
|
||||
"versionPragmas": [
|
||||
"^0.8.20"
|
||||
],
|
||||
"artifacts": [
|
||||
"ECDSA"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol": {
|
||||
"lastModificationDate": 1754306768254,
|
||||
"contentHash": "51c2083b160453420aaa0a046c16d5ca",
|
||||
"sourceName": "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol",
|
||||
"solcConfig": {
|
||||
"version": "0.8.20",
|
||||
"settings": {
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 200
|
||||
},
|
||||
"viaIR": true,
|
||||
"evmVersion": "paris",
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
||||
"abi",
|
||||
"evm.bytecode",
|
||||
"evm.deployedBytecode",
|
||||
"evm.methodIdentifiers",
|
||||
"metadata"
|
||||
],
|
||||
"": [
|
||||
"ast"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"../ERC20.sol",
|
||||
"../../../governance/utils/Votes.sol",
|
||||
"../../../utils/structs/Checkpoints.sol"
|
||||
],
|
||||
"versionPragmas": [
|
||||
"^0.8.20"
|
||||
],
|
||||
"artifacts": [
|
||||
"ERC20Votes"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/utils/Nonces.sol": {
|
||||
"lastModificationDate": 1754306760451,
|
||||
"contentHash": "c32d108058718efb9061b88e83a83f79",
|
||||
"sourceName": "@openzeppelin/contracts/utils/Nonces.sol",
|
||||
"solcConfig": {
|
||||
"version": "0.8.20",
|
||||
"settings": {
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 200
|
||||
},
|
||||
"viaIR": true,
|
||||
"evmVersion": "paris",
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
||||
"abi",
|
||||
"evm.bytecode",
|
||||
"evm.deployedBytecode",
|
||||
"evm.methodIdentifiers",
|
||||
"metadata"
|
||||
],
|
||||
"": [
|
||||
"ast"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"imports": [],
|
||||
"versionPragmas": [
|
||||
"^0.8.20"
|
||||
],
|
||||
"artifacts": [
|
||||
"Nonces"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/utils/cryptography/EIP712.sol": {
|
||||
"lastModificationDate": 1754306764456,
|
||||
"contentHash": "8dbb261c55f358342798c4d1803d4f8e",
|
||||
"sourceName": "@openzeppelin/contracts/utils/cryptography/EIP712.sol",
|
||||
"solcConfig": {
|
||||
"version": "0.8.20",
|
||||
"settings": {
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 200
|
||||
},
|
||||
"viaIR": true,
|
||||
"evmVersion": "paris",
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
||||
"abi",
|
||||
"evm.bytecode",
|
||||
"evm.deployedBytecode",
|
||||
"evm.methodIdentifiers",
|
||||
"metadata"
|
||||
],
|
||||
"": [
|
||||
"ast"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"./MessageHashUtils.sol",
|
||||
"../ShortStrings.sol",
|
||||
"../../interfaces/IERC5267.sol"
|
||||
],
|
||||
"versionPragmas": [
|
||||
"^0.8.20"
|
||||
],
|
||||
"artifacts": [
|
||||
"EIP712"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol": {
|
||||
"lastModificationDate": 1754306768254,
|
||||
"contentHash": "94ec15baf0d5df863f45b8f351937ec7",
|
||||
"sourceName": "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol",
|
||||
"solcConfig": {
|
||||
"version": "0.8.20",
|
||||
"settings": {
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 200
|
||||
},
|
||||
"viaIR": true,
|
||||
"evmVersion": "paris",
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
||||
"abi",
|
||||
"evm.bytecode",
|
||||
"evm.deployedBytecode",
|
||||
"evm.methodIdentifiers",
|
||||
"metadata"
|
||||
],
|
||||
"": [
|
||||
"ast"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"imports": [],
|
||||
"versionPragmas": [
|
||||
"^0.8.20"
|
||||
],
|
||||
"artifacts": [
|
||||
"IERC20Permit"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/interfaces/IERC5267.sol": {
|
||||
"lastModificationDate": 1754306760460,
|
||||
"contentHash": "94364524cb1a39dcbc3d3afff6d8e53e",
|
||||
"sourceName": "@openzeppelin/contracts/interfaces/IERC5267.sol",
|
||||
"solcConfig": {
|
||||
"version": "0.8.20",
|
||||
"settings": {
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 200
|
||||
},
|
||||
"viaIR": true,
|
||||
"evmVersion": "paris",
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
||||
"abi",
|
||||
"evm.bytecode",
|
||||
"evm.deployedBytecode",
|
||||
"evm.methodIdentifiers",
|
||||
"metadata"
|
||||
],
|
||||
"": [
|
||||
"ast"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"imports": [],
|
||||
"versionPragmas": [
|
||||
"^0.8.20"
|
||||
],
|
||||
"artifacts": [
|
||||
"IERC5267"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/utils/ShortStrings.sol": {
|
||||
"lastModificationDate": 1754306760451,
|
||||
"contentHash": "94e7feaf138d08fb736e43ca0be9bf26",
|
||||
"sourceName": "@openzeppelin/contracts/utils/ShortStrings.sol",
|
||||
"solcConfig": {
|
||||
"version": "0.8.20",
|
||||
"settings": {
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 200
|
||||
},
|
||||
"viaIR": true,
|
||||
"evmVersion": "paris",
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
||||
"abi",
|
||||
"evm.bytecode",
|
||||
"evm.deployedBytecode",
|
||||
"evm.methodIdentifiers",
|
||||
"metadata"
|
||||
],
|
||||
"": [
|
||||
"ast"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"./StorageSlot.sol"
|
||||
],
|
||||
"versionPragmas": [
|
||||
"^0.8.20"
|
||||
],
|
||||
"artifacts": [
|
||||
"ShortStrings"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol": {
|
||||
"lastModificationDate": 1754306764465,
|
||||
"contentHash": "86fd93657e4e27ff76c38699e9b9fcef",
|
||||
"sourceName": "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol",
|
||||
"solcConfig": {
|
||||
"version": "0.8.20",
|
||||
"settings": {
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 200
|
||||
},
|
||||
"viaIR": true,
|
||||
"evmVersion": "paris",
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
||||
"abi",
|
||||
"evm.bytecode",
|
||||
"evm.deployedBytecode",
|
||||
"evm.methodIdentifiers",
|
||||
"metadata"
|
||||
],
|
||||
"": [
|
||||
"ast"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"../Strings.sol"
|
||||
],
|
||||
"versionPragmas": [
|
||||
"^0.8.20"
|
||||
],
|
||||
"artifacts": [
|
||||
"MessageHashUtils"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/utils/StorageSlot.sol": {
|
||||
"lastModificationDate": 1754306760451,
|
||||
"contentHash": "e656d64c4ce918f3d13030b91c935134",
|
||||
"sourceName": "@openzeppelin/contracts/utils/StorageSlot.sol",
|
||||
"solcConfig": {
|
||||
"version": "0.8.20",
|
||||
"settings": {
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 200
|
||||
},
|
||||
"viaIR": true,
|
||||
"evmVersion": "paris",
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
||||
"abi",
|
||||
"evm.bytecode",
|
||||
"evm.deployedBytecode",
|
||||
"evm.methodIdentifiers",
|
||||
"metadata"
|
||||
],
|
||||
"": [
|
||||
"ast"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"imports": [],
|
||||
"versionPragmas": [
|
||||
"^0.8.20"
|
||||
],
|
||||
"artifacts": [
|
||||
"StorageSlot"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/utils/Strings.sol": {
|
||||
"lastModificationDate": 1754306760451,
|
||||
"contentHash": "a55fef2557b35bac18a1880d3c2e6740",
|
||||
"sourceName": "@openzeppelin/contracts/utils/Strings.sol",
|
||||
"solcConfig": {
|
||||
"version": "0.8.20",
|
||||
"settings": {
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 200
|
||||
},
|
||||
"viaIR": true,
|
||||
"evmVersion": "paris",
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
||||
"abi",
|
||||
"evm.bytecode",
|
||||
"evm.deployedBytecode",
|
||||
"evm.methodIdentifiers",
|
||||
"metadata"
|
||||
],
|
||||
"": [
|
||||
"ast"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"./math/Math.sol",
|
||||
"./math/SafeCast.sol",
|
||||
"./math/SignedMath.sol"
|
||||
],
|
||||
"versionPragmas": [
|
||||
"^0.8.20"
|
||||
],
|
||||
"artifacts": [
|
||||
"Strings"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/utils/math/Math.sol": {
|
||||
"lastModificationDate": 1754306764456,
|
||||
"contentHash": "2b2665ae9bdb1af440658741a77fe213",
|
||||
"sourceName": "@openzeppelin/contracts/utils/math/Math.sol",
|
||||
"solcConfig": {
|
||||
"version": "0.8.20",
|
||||
"settings": {
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 200
|
||||
},
|
||||
"viaIR": true,
|
||||
"evmVersion": "paris",
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
||||
"abi",
|
||||
"evm.bytecode",
|
||||
"evm.deployedBytecode",
|
||||
"evm.methodIdentifiers",
|
||||
"metadata"
|
||||
],
|
||||
"": [
|
||||
"ast"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"../Panic.sol",
|
||||
"./SafeCast.sol"
|
||||
],
|
||||
"versionPragmas": [
|
||||
"^0.8.20"
|
||||
],
|
||||
"artifacts": [
|
||||
"Math"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/utils/math/SafeCast.sol": {
|
||||
"lastModificationDate": 1754306764456,
|
||||
"contentHash": "2adca1150f58fc6f3d1f0a0f22ee7cca",
|
||||
"sourceName": "@openzeppelin/contracts/utils/math/SafeCast.sol",
|
||||
"solcConfig": {
|
||||
"version": "0.8.20",
|
||||
"settings": {
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 200
|
||||
},
|
||||
"viaIR": true,
|
||||
"evmVersion": "paris",
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
||||
"abi",
|
||||
"evm.bytecode",
|
||||
"evm.deployedBytecode",
|
||||
"evm.methodIdentifiers",
|
||||
"metadata"
|
||||
],
|
||||
"": [
|
||||
"ast"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"imports": [],
|
||||
"versionPragmas": [
|
||||
"^0.8.20"
|
||||
],
|
||||
"artifacts": [
|
||||
"SafeCast"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/utils/math/SignedMath.sol": {
|
||||
"lastModificationDate": 1754306764456,
|
||||
"contentHash": "ae3528afb8bdb0a7dcfba5b115ee8074",
|
||||
"sourceName": "@openzeppelin/contracts/utils/math/SignedMath.sol",
|
||||
"solcConfig": {
|
||||
"version": "0.8.20",
|
||||
"settings": {
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 200
|
||||
},
|
||||
"viaIR": true,
|
||||
"evmVersion": "paris",
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
||||
"abi",
|
||||
"evm.bytecode",
|
||||
"evm.deployedBytecode",
|
||||
"evm.methodIdentifiers",
|
||||
"metadata"
|
||||
],
|
||||
"": [
|
||||
"ast"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"./SafeCast.sol"
|
||||
],
|
||||
"versionPragmas": [
|
||||
"^0.8.20"
|
||||
],
|
||||
"artifacts": [
|
||||
"SignedMath"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/utils/Panic.sol": {
|
||||
"lastModificationDate": 1754306760451,
|
||||
"contentHash": "2133dc13536b4a6a98131e431fac59e1",
|
||||
"sourceName": "@openzeppelin/contracts/utils/Panic.sol",
|
||||
"solcConfig": {
|
||||
"version": "0.8.20",
|
||||
"settings": {
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 200
|
||||
},
|
||||
"viaIR": true,
|
||||
"evmVersion": "paris",
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
||||
"abi",
|
||||
"evm.bytecode",
|
||||
"evm.deployedBytecode",
|
||||
"evm.methodIdentifiers",
|
||||
"metadata"
|
||||
],
|
||||
"": [
|
||||
"ast"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"imports": [],
|
||||
"versionPragmas": [
|
||||
"^0.8.20"
|
||||
],
|
||||
"artifacts": [
|
||||
"Panic"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/governance/utils/Votes.sol": {
|
||||
"lastModificationDate": 1754306764456,
|
||||
"contentHash": "95aceafdc639babdd22576e5e3774d64",
|
||||
"sourceName": "@openzeppelin/contracts/governance/utils/Votes.sol",
|
||||
"solcConfig": {
|
||||
"version": "0.8.20",
|
||||
"settings": {
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 200
|
||||
},
|
||||
"viaIR": true,
|
||||
"evmVersion": "paris",
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
||||
"abi",
|
||||
"evm.bytecode",
|
||||
"evm.deployedBytecode",
|
||||
"evm.methodIdentifiers",
|
||||
"metadata"
|
||||
],
|
||||
"": [
|
||||
"ast"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"../../interfaces/IERC5805.sol",
|
||||
"../../utils/Context.sol",
|
||||
"../../utils/Nonces.sol",
|
||||
"../../utils/cryptography/EIP712.sol",
|
||||
"../../utils/structs/Checkpoints.sol",
|
||||
"../../utils/math/SafeCast.sol",
|
||||
"../../utils/cryptography/ECDSA.sol",
|
||||
"../../utils/types/Time.sol"
|
||||
],
|
||||
"versionPragmas": [
|
||||
"^0.8.20"
|
||||
],
|
||||
"artifacts": [
|
||||
"Votes"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/utils/structs/Checkpoints.sol": {
|
||||
"lastModificationDate": 1754306764456,
|
||||
"contentHash": "657c6dfea3bae1af948de6113ba01cea",
|
||||
"sourceName": "@openzeppelin/contracts/utils/structs/Checkpoints.sol",
|
||||
"solcConfig": {
|
||||
"version": "0.8.20",
|
||||
"settings": {
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 200
|
||||
},
|
||||
"viaIR": true,
|
||||
"evmVersion": "paris",
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
||||
"abi",
|
||||
"evm.bytecode",
|
||||
"evm.deployedBytecode",
|
||||
"evm.methodIdentifiers",
|
||||
"metadata"
|
||||
],
|
||||
"": [
|
||||
"ast"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"../math/Math.sol"
|
||||
],
|
||||
"versionPragmas": [
|
||||
"^0.8.20"
|
||||
],
|
||||
"artifacts": [
|
||||
"Checkpoints"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/interfaces/IERC5805.sol": {
|
||||
"lastModificationDate": 1754306760460,
|
||||
"contentHash": "65ba9f89b1057e2192e341b286d4e261",
|
||||
"sourceName": "@openzeppelin/contracts/interfaces/IERC5805.sol",
|
||||
"solcConfig": {
|
||||
"version": "0.8.20",
|
||||
"settings": {
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 200
|
||||
},
|
||||
"viaIR": true,
|
||||
"evmVersion": "paris",
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
||||
"abi",
|
||||
"evm.bytecode",
|
||||
"evm.deployedBytecode",
|
||||
"evm.methodIdentifiers",
|
||||
"metadata"
|
||||
],
|
||||
"": [
|
||||
"ast"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"../governance/utils/IVotes.sol",
|
||||
"./IERC6372.sol"
|
||||
],
|
||||
"versionPragmas": [
|
||||
"^0.8.20"
|
||||
],
|
||||
"artifacts": [
|
||||
"IERC5805"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/utils/types/Time.sol": {
|
||||
"lastModificationDate": 1754306764456,
|
||||
"contentHash": "d83e7814a059fc1287fd765f424ce004",
|
||||
"sourceName": "@openzeppelin/contracts/utils/types/Time.sol",
|
||||
"solcConfig": {
|
||||
"version": "0.8.20",
|
||||
"settings": {
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 200
|
||||
},
|
||||
"viaIR": true,
|
||||
"evmVersion": "paris",
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
||||
"abi",
|
||||
"evm.bytecode",
|
||||
"evm.deployedBytecode",
|
||||
"evm.methodIdentifiers",
|
||||
"metadata"
|
||||
],
|
||||
"": [
|
||||
"ast"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"../math/Math.sol",
|
||||
"../math/SafeCast.sol"
|
||||
],
|
||||
"versionPragmas": [
|
||||
"^0.8.20"
|
||||
],
|
||||
"artifacts": [
|
||||
"Time"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/governance/utils/IVotes.sol": {
|
||||
"lastModificationDate": 1754306764456,
|
||||
"contentHash": "905ffceb29869fee4b5a649abe7e2927",
|
||||
"sourceName": "@openzeppelin/contracts/governance/utils/IVotes.sol",
|
||||
"solcConfig": {
|
||||
"version": "0.8.20",
|
||||
"settings": {
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 200
|
||||
},
|
||||
"viaIR": true,
|
||||
"evmVersion": "paris",
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
||||
"abi",
|
||||
"evm.bytecode",
|
||||
"evm.deployedBytecode",
|
||||
"evm.methodIdentifiers",
|
||||
"metadata"
|
||||
],
|
||||
"": [
|
||||
"ast"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"imports": [],
|
||||
"versionPragmas": [
|
||||
"^0.8.20"
|
||||
],
|
||||
"artifacts": [
|
||||
"IVotes"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/interfaces/IERC6372.sol": {
|
||||
"lastModificationDate": 1754306760460,
|
||||
"contentHash": "414cd6acf090e4009cf016ff62ecbd88",
|
||||
"sourceName": "@openzeppelin/contracts/interfaces/IERC6372.sol",
|
||||
"solcConfig": {
|
||||
"version": "0.8.20",
|
||||
"settings": {
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 200
|
||||
},
|
||||
"viaIR": true,
|
||||
"evmVersion": "paris",
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
||||
"abi",
|
||||
"evm.bytecode",
|
||||
"evm.deployedBytecode",
|
||||
"evm.methodIdentifiers",
|
||||
"metadata"
|
||||
],
|
||||
"": [
|
||||
"ast"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"imports": [],
|
||||
"versionPragmas": [
|
||||
"^0.8.20"
|
||||
],
|
||||
"artifacts": [
|
||||
"IERC6372"
|
||||
]
|
||||
},
|
||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/contracts/FactoryDeployer.sol": {
|
||||
"lastModificationDate": 1754683537642,
|
||||
"contentHash": "6161bbb21af830bc05d6acd8682d9678",
|
||||
"sourceName": "contracts/FactoryDeployer.sol",
|
||||
"solcConfig": {
|
||||
"version": "0.8.20",
|
||||
"settings": {
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 200
|
||||
},
|
||||
"viaIR": true,
|
||||
"evmVersion": "paris",
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
||||
"abi",
|
||||
"evm.bytecode",
|
||||
"evm.deployedBytecode",
|
||||
"evm.methodIdentifiers",
|
||||
"metadata"
|
||||
],
|
||||
"": [
|
||||
"ast"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"imports": [],
|
||||
"versionPragmas": [
|
||||
"^0.8.20"
|
||||
],
|
||||
"artifacts": [
|
||||
"FactoryDeployer"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,14 +11,18 @@
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";
|
||||
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
|
||||
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
|
||||
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
|
||||
|
||||
/**
|
||||
* @title DLE (Digital Legal Entity)
|
||||
* @dev Основной контракт DLE с модульной архитектурой и мульти-чейн поддержкой
|
||||
* @dev Основной контракт DLE с модульной архитектурой, Single-Chain Governance
|
||||
* и безопасной мульти-чейн синхронизацией без сторонних мостов (через подписи холдеров).
|
||||
*/
|
||||
contract DLE is ERC20, ReentrancyGuard {
|
||||
contract DLE is ERC20, ERC20Permit, ERC20Votes, ReentrancyGuard {
|
||||
using ECDSA for bytes32;
|
||||
struct DLEInfo {
|
||||
string name;
|
||||
string symbol;
|
||||
@@ -53,11 +57,15 @@ contract DLE is ERC20, ReentrancyGuard {
|
||||
uint256 forVotes;
|
||||
uint256 againstVotes;
|
||||
bool executed;
|
||||
uint256 deadline;
|
||||
bool canceled;
|
||||
uint256 deadline; // конец периода голосования (sec)
|
||||
address initiator;
|
||||
bytes operation; // Операция для исполнения
|
||||
bytes operation; // операция для исполнения
|
||||
uint256 governanceChainId; // сеть голосования (Single-Chain Governance)
|
||||
uint256[] targetChains; // целевые сети для исполнения
|
||||
uint256 timelock; // earliest execution timestamp (sec)
|
||||
uint256 snapshotTimepoint; // блок/временная точка для getPastVotes
|
||||
mapping(address => bool) hasVoted;
|
||||
mapping(uint256 => bool) chainVoteSynced; // Синхронизация голосов между цепочками
|
||||
}
|
||||
|
||||
|
||||
@@ -74,15 +82,11 @@ contract DLE is ERC20, ReentrancyGuard {
|
||||
|
||||
// Предложения
|
||||
mapping(uint256 => Proposal) public proposals;
|
||||
uint256[] public allProposalIds;
|
||||
|
||||
// Мульти-чейн
|
||||
mapping(uint256 => bool) public supportedChains;
|
||||
uint256[] public supportedChainIds;
|
||||
mapping(uint256 => bool) public executedProposals; // Синхронизация исполненных предложений
|
||||
|
||||
// Merkle proofs для cross-chain синхронизации
|
||||
mapping(uint256 => bytes32) public chainMerkleRoots; // chainId => merkleRoot
|
||||
mapping(uint256 => mapping(uint256 => bool)) public processedProofs; // proposalId => proofHash => processed
|
||||
|
||||
// События
|
||||
event DLEInitialized(
|
||||
@@ -101,21 +105,29 @@ contract DLE is ERC20, ReentrancyGuard {
|
||||
event ProposalCreated(uint256 proposalId, address initiator, string description);
|
||||
event ProposalVoted(uint256 proposalId, address voter, bool support, uint256 votingPower);
|
||||
event ProposalExecuted(uint256 proposalId, bytes operation);
|
||||
event ProposalCancelled(uint256 proposalId, string reason);
|
||||
event ProposalTimelockSet(uint256 proposalId, uint256 timelock);
|
||||
event ProposalTargetsSet(uint256 proposalId, uint256[] targetChains);
|
||||
event ProposalGovernanceChainSet(uint256 proposalId, uint256 governanceChainId);
|
||||
event ModuleAdded(bytes32 moduleId, address moduleAddress);
|
||||
event ModuleRemoved(bytes32 moduleId);
|
||||
event CrossChainExecutionSync(uint256 proposalId, uint256 fromChainId, uint256 toChainId);
|
||||
event CrossChainVoteSync(uint256 proposalId, uint256 fromChainId, uint256 toChainId);
|
||||
event ProposalExecutionApprovedInChain(uint256 proposalId, uint256 chainId);
|
||||
event ChainAdded(uint256 chainId);
|
||||
event ChainRemoved(uint256 chainId);
|
||||
event ChainMerkleRootSet(uint256 chainId, bytes32 merkleRoot);
|
||||
event DLEInfoUpdated(string name, string symbol, string location, string coordinates, uint256 jurisdiction, uint256 oktmo, string[] okvedCodes, uint256 kpp);
|
||||
event QuorumPercentageUpdated(uint256 oldQuorumPercentage, uint256 newQuorumPercentage);
|
||||
event CurrentChainIdUpdated(uint256 oldChainId, uint256 newChainId);
|
||||
|
||||
// EIP712 typehash для подписи одобрения исполнения предложения в целевой сети
|
||||
// ExecutionApproval(uint256 proposalId, bytes32 operationHash, uint256 chainId, uint256 snapshotTimepoint)
|
||||
bytes32 private constant EXECUTION_APPROVAL_TYPEHASH = keccak256(
|
||||
"ExecutionApproval(uint256 proposalId,bytes32 operationHash,uint256 chainId,uint256 snapshotTimepoint)"
|
||||
);
|
||||
|
||||
constructor(
|
||||
DLEConfig memory config,
|
||||
uint256 _currentChainId
|
||||
) ERC20(config.name, config.symbol) {
|
||||
) ERC20(config.name, config.symbol) ERC20Permit(config.name) {
|
||||
dleInfo = DLEInfo({
|
||||
name: config.name,
|
||||
symbol: config.symbol,
|
||||
@@ -143,9 +155,13 @@ contract DLE is ERC20, ReentrancyGuard {
|
||||
require(config.initialPartners.length > 0, "No initial partners");
|
||||
|
||||
for (uint256 i = 0; i < config.initialPartners.length; i++) {
|
||||
require(config.initialPartners[i] != address(0), "Zero address");
|
||||
require(config.initialAmounts[i] > 0, "Zero amount");
|
||||
_mint(config.initialPartners[i], config.initialAmounts[i]);
|
||||
address partner = config.initialPartners[i];
|
||||
uint256 amount = config.initialAmounts[i];
|
||||
require(partner != address(0), "Zero address");
|
||||
require(amount > 0, "Zero amount");
|
||||
_mint(partner, amount);
|
||||
// Авто-делегирование голосов себе, чтобы getPastVotes работал без действия пользователя
|
||||
_delegate(partner, partner);
|
||||
}
|
||||
|
||||
emit InitialTokensDistributed(config.initialPartners, config.initialAmounts);
|
||||
@@ -174,12 +190,14 @@ contract DLE is ERC20, ReentrancyGuard {
|
||||
string memory _description,
|
||||
uint256 _duration,
|
||||
bytes memory _operation,
|
||||
uint256 _governanceChainId
|
||||
uint256 _governanceChainId,
|
||||
uint256[] memory _targetChains,
|
||||
uint256 _timelockDelay
|
||||
) external returns (uint256) {
|
||||
require(balanceOf(msg.sender) > 0, "Must hold tokens to create proposal");
|
||||
require(_duration > 0, "Duration must be positive");
|
||||
require(supportedChains[_governanceChainId], "Chain not supported");
|
||||
require(checkChainConnection(_governanceChainId), "Chain not available");
|
||||
require(_timelockDelay <= 365 days, "Timelock too big");
|
||||
|
||||
uint256 proposalId = proposalCounter++;
|
||||
Proposal storage proposal = proposals[proposalId];
|
||||
@@ -192,8 +210,22 @@ contract DLE is ERC20, ReentrancyGuard {
|
||||
proposal.deadline = block.timestamp + _duration;
|
||||
proposal.initiator = msg.sender;
|
||||
proposal.operation = _operation;
|
||||
proposal.governanceChainId = _governanceChainId;
|
||||
proposal.timelock = block.timestamp + _timelockDelay;
|
||||
// Снимок голосов: используем прошлую точку времени, чтобы getPastVotes был валиден в текущем блоке
|
||||
uint256 nowClock = clock();
|
||||
proposal.snapshotTimepoint = nowClock == 0 ? 0 : nowClock - 1;
|
||||
// запись целевых сетей
|
||||
for (uint256 i = 0; i < _targetChains.length; i++) {
|
||||
require(supportedChains[_targetChains[i]], "Target chain not supported");
|
||||
proposal.targetChains.push(_targetChains[i]);
|
||||
}
|
||||
|
||||
allProposalIds.push(proposalId);
|
||||
emit ProposalCreated(proposalId, msg.sender, _description);
|
||||
emit ProposalGovernanceChainSet(proposalId, _governanceChainId);
|
||||
emit ProposalTargetsSet(proposalId, _targetChains);
|
||||
emit ProposalTimelockSet(proposalId, proposal.timelock);
|
||||
return proposalId;
|
||||
}
|
||||
|
||||
@@ -208,9 +240,10 @@ contract DLE is ERC20, ReentrancyGuard {
|
||||
require(block.timestamp < proposal.deadline, "Voting ended");
|
||||
require(!proposal.executed, "Proposal already executed");
|
||||
require(!proposal.hasVoted[msg.sender], "Already voted");
|
||||
require(balanceOf(msg.sender) > 0, "No tokens to vote");
|
||||
require(currentChainId == proposal.governanceChainId, "Wrong chain for voting");
|
||||
|
||||
uint256 votingPower = balanceOf(msg.sender);
|
||||
// используем снапшот голосов для защиты от перелива
|
||||
uint256 votingPower = getPastVotes(msg.sender, proposal.snapshotTimepoint);
|
||||
proposal.hasVoted[msg.sender] = true;
|
||||
|
||||
if (_support) {
|
||||
@@ -222,53 +255,7 @@ contract DLE is ERC20, ReentrancyGuard {
|
||||
emit ProposalVoted(_proposalId, msg.sender, _support, votingPower);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Синхронизировать голос из другой цепочки
|
||||
* @param _proposalId ID предложения
|
||||
* @param _fromChainId ID цепочки откуда синхронизируем
|
||||
* @param _forVotes Голоса за
|
||||
* @param _againstVotes Голоса против
|
||||
*/
|
||||
function syncVoteFromChain(
|
||||
uint256 _proposalId,
|
||||
uint256 _fromChainId,
|
||||
uint256 _forVotes,
|
||||
uint256 _againstVotes,
|
||||
bytes memory _proof
|
||||
) external {
|
||||
Proposal storage proposal = proposals[_proposalId];
|
||||
require(proposal.id == _proposalId, "Proposal does not exist");
|
||||
require(supportedChains[_fromChainId], "Chain not supported");
|
||||
require(!proposal.chainVoteSynced[_fromChainId], "Already synced");
|
||||
|
||||
// Проверяем доказательство cross-chain синхронизации
|
||||
require(_proof.length > 0, "Proof required for cross-chain sync");
|
||||
|
||||
// Проверяем Merkle proof для cross-chain синхронизации
|
||||
bytes32 proofHash = keccak256(abi.encodePacked(_proposalId, _fromChainId, _forVotes, _againstVotes));
|
||||
require(!processedProofs[_proposalId][uint256(proofHash)], "Proof already processed");
|
||||
|
||||
// Проверяем, что Merkle root для цепочки установлен
|
||||
bytes32 merkleRoot = chainMerkleRoots[_fromChainId];
|
||||
require(merkleRoot != bytes32(0), "Merkle root not set for chain");
|
||||
|
||||
// Проверяем Merkle proof
|
||||
bytes32[] memory proof = abi.decode(_proof, (bytes32[]));
|
||||
require(MerkleProof.verify(proof, merkleRoot, proofHash), "Invalid Merkle proof");
|
||||
|
||||
// Отмечаем proof как обработанный
|
||||
processedProofs[_proposalId][uint256(proofHash)] = true;
|
||||
|
||||
// Проверяем, что голоса не превышают общее количество токенов
|
||||
uint256 totalVotes = _forVotes + _againstVotes;
|
||||
require(totalVotes <= totalSupply(), "Votes exceed total supply");
|
||||
|
||||
proposal.forVotes += _forVotes;
|
||||
proposal.againstVotes += _againstVotes;
|
||||
proposal.chainVoteSynced[_fromChainId] = true;
|
||||
|
||||
emit CrossChainVoteSync(_proposalId, _fromChainId, currentChainId);
|
||||
}
|
||||
// УДАЛЕНО: syncVoteFromChain с MerkleProof — небезопасно без доверенного моста
|
||||
|
||||
/**
|
||||
* @dev Проверить результат предложения
|
||||
@@ -281,7 +268,9 @@ contract DLE is ERC20, ReentrancyGuard {
|
||||
require(proposal.id == _proposalId, "Proposal does not exist");
|
||||
|
||||
uint256 totalVotes = proposal.forVotes + proposal.againstVotes;
|
||||
uint256 quorumRequired = (totalSupply() * quorumPercentage) / 100;
|
||||
// Используем снапшот totalSupply на момент начала голосования
|
||||
uint256 pastSupply = getPastTotalSupply(proposal.snapshotTimepoint);
|
||||
uint256 quorumRequired = (pastSupply * quorumPercentage) / 100;
|
||||
|
||||
quorumReached = totalVotes >= quorumRequired;
|
||||
passed = quorumReached && proposal.forVotes > proposal.againstVotes;
|
||||
@@ -297,6 +286,7 @@ contract DLE is ERC20, ReentrancyGuard {
|
||||
Proposal storage proposal = proposals[_proposalId];
|
||||
require(proposal.id == _proposalId, "Proposal does not exist");
|
||||
require(!proposal.executed, "Proposal already executed");
|
||||
require(currentChainId == proposal.governanceChainId, "Execute only in governance chain");
|
||||
|
||||
(bool passed, bool quorumReached) = checkProposalResult(_proposalId);
|
||||
|
||||
@@ -307,6 +297,7 @@ contract DLE is ERC20, ReentrancyGuard {
|
||||
"Voting not ended and quorum not reached"
|
||||
);
|
||||
require(passed && quorumReached, "Proposal not passed");
|
||||
require(block.timestamp >= proposal.timelock, "Timelock not expired");
|
||||
|
||||
proposal.executed = true;
|
||||
|
||||
@@ -317,49 +308,74 @@ contract DLE is ERC20, ReentrancyGuard {
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Синхронизировать исполнение из другой цепочки
|
||||
* @param _proposalId ID предложения
|
||||
* @param _fromChainId ID цепочки откуда синхронизируем
|
||||
* @dev Отмена предложения до истечения голосования инициатором при наличии достаточной голосующей силы.
|
||||
* Это soft-cancel для защиты от явных ошибок. Порог: >= 10% от снапшотного supply.
|
||||
*/
|
||||
function syncExecutionFromChain(
|
||||
uint256 _proposalId,
|
||||
uint256 _fromChainId,
|
||||
bytes memory _proof
|
||||
) external {
|
||||
require(supportedChains[_fromChainId], "Chain not supported");
|
||||
require(!executedProposals[_proposalId], "Already executed");
|
||||
function cancelProposal(uint256 _proposalId, string calldata reason) external {
|
||||
Proposal storage proposal = proposals[_proposalId];
|
||||
require(proposal.id == _proposalId, "Proposal does not exist");
|
||||
require(!proposal.executed, "Already executed");
|
||||
require(block.timestamp < proposal.deadline, "Voting ended");
|
||||
require(msg.sender == proposal.initiator, "Only initiator");
|
||||
uint256 vp = getPastVotes(msg.sender, proposal.snapshotTimepoint);
|
||||
uint256 pastSupply = getPastTotalSupply(proposal.snapshotTimepoint);
|
||||
require(vp * 10 >= pastSupply, "Insufficient voting power to cancel");
|
||||
|
||||
// Проверяем доказательство исполнения из другой цепочки
|
||||
require(_proof.length > 0, "Proof required for cross-chain execution");
|
||||
|
||||
// Проверяем Merkle proof для cross-chain исполнения
|
||||
bytes32 proofHash = keccak256(abi.encodePacked(_proposalId, _fromChainId, "EXECUTION"));
|
||||
require(!processedProofs[_proposalId][uint256(proofHash)], "Proof already processed");
|
||||
|
||||
// Проверяем, что Merkle root для цепочки установлен
|
||||
bytes32 merkleRoot = chainMerkleRoots[_fromChainId];
|
||||
require(merkleRoot != bytes32(0), "Merkle root not set for chain");
|
||||
|
||||
// Проверяем Merkle proof
|
||||
bytes32[] memory proof = abi.decode(_proof, (bytes32[]));
|
||||
require(MerkleProof.verify(proof, merkleRoot, proofHash), "Invalid Merkle proof");
|
||||
|
||||
// Отмечаем proof как обработанный
|
||||
processedProofs[_proposalId][uint256(proofHash)] = true;
|
||||
|
||||
// Проверяем, что предложение существует и не было исполнено
|
||||
proposal.canceled = true;
|
||||
emit ProposalCancelled(_proposalId, reason);
|
||||
}
|
||||
|
||||
// УДАЛЕНО: syncExecutionFromChain с MerkleProof — небезопасно без доверенного моста
|
||||
|
||||
/**
|
||||
* @dev Исполнение предложения в НЕ governance-сети по подписям холдеров на снапшоте.
|
||||
* Подходит для target chains. Не требует внешнего моста.
|
||||
*/
|
||||
function executeProposalBySignatures(
|
||||
uint256 _proposalId,
|
||||
address[] calldata signers,
|
||||
bytes[] calldata signatures
|
||||
) external nonReentrant {
|
||||
Proposal storage proposal = proposals[_proposalId];
|
||||
require(proposal.id == _proposalId, "Proposal does not exist");
|
||||
require(!proposal.executed, "Proposal already executed");
|
||||
require(!proposal.executed, "Proposal already executed in this chain");
|
||||
require(currentChainId != proposal.governanceChainId, "Use executeProposal in governance chain");
|
||||
require(_isTargetChain(proposal, currentChainId), "Chain not in targets");
|
||||
require(block.timestamp >= proposal.timelock, "Timelock not expired");
|
||||
|
||||
executedProposals[_proposalId] = true;
|
||||
|
||||
// Исполняем операцию из предложения
|
||||
if (proposal.id == _proposalId) {
|
||||
_executeOperation(proposal.operation);
|
||||
require(signers.length == signatures.length, "Bad signatures");
|
||||
bytes32 opHash = keccak256(proposal.operation);
|
||||
bytes32 structHash = keccak256(abi.encode(
|
||||
EXECUTION_APPROVAL_TYPEHASH,
|
||||
_proposalId,
|
||||
opHash,
|
||||
currentChainId,
|
||||
proposal.snapshotTimepoint
|
||||
));
|
||||
bytes32 digest = _hashTypedDataV4(structHash);
|
||||
|
||||
uint256 votesFor = 0;
|
||||
// простая защита от дублей адресов (O(n^2) по малому n)
|
||||
for (uint256 i = 0; i < signers.length; i++) {
|
||||
address recovered = ECDSA.recover(digest, signatures[i]);
|
||||
require(recovered == signers[i], "Bad signature");
|
||||
// проверка на дубли
|
||||
for (uint256 j = 0; j < i; j++) {
|
||||
require(signers[j] != recovered, "Duplicate signer");
|
||||
}
|
||||
uint256 vp = getPastVotes(recovered, proposal.snapshotTimepoint);
|
||||
require(vp > 0, "No voting power at snapshot");
|
||||
votesFor += vp;
|
||||
}
|
||||
|
||||
emit CrossChainExecutionSync(_proposalId, _fromChainId, currentChainId);
|
||||
uint256 pastSupply = getPastTotalSupply(proposal.snapshotTimepoint);
|
||||
uint256 quorumRequired = (pastSupply * quorumPercentage) / 100;
|
||||
require(votesFor >= quorumRequired, "Quorum not reached by sigs");
|
||||
|
||||
proposal.executed = true;
|
||||
_executeOperation(proposal.operation);
|
||||
emit ProposalExecuted(_proposalId, proposal.operation);
|
||||
emit ProposalExecutionApprovedInChain(_proposalId, currentChainId);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -368,19 +384,8 @@ contract DLE is ERC20, ReentrancyGuard {
|
||||
* @return isAvailable Доступна ли цепочка
|
||||
*/
|
||||
function checkChainConnection(uint256 _chainId) public view returns (bool isAvailable) {
|
||||
// Проверяем, поддерживается ли цепочка
|
||||
if (!supportedChains[_chainId]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Проверяем, что Merkle root установлен для цепочки
|
||||
// Это означает, что цепочка активна и готова к синхронизации
|
||||
bytes32 merkleRoot = chainMerkleRoots[_chainId];
|
||||
if (merkleRoot == bytes32(0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
// Упрощенная проверка: цепочка объявлена как поддерживаемая
|
||||
return supportedChains[_chainId];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -410,12 +415,8 @@ contract DLE is ERC20, ReentrancyGuard {
|
||||
function syncToAllChains(uint256 _proposalId) external {
|
||||
require(checkSyncReadiness(_proposalId), "Not all chains ready");
|
||||
|
||||
// Выполняем синхронизацию во все цепочки
|
||||
for (uint256 i = 0; i < getSupportedChainCount(); i++) {
|
||||
uint256 chainId = getSupportedChainId(i);
|
||||
syncToChain(_proposalId, chainId);
|
||||
}
|
||||
|
||||
// В этой версии без внешнего моста синхронизация выполняется
|
||||
// через executeProposalBySignatures в целевых сетях.
|
||||
emit SyncCompleted(_proposalId);
|
||||
}
|
||||
|
||||
@@ -424,26 +425,7 @@ contract DLE is ERC20, ReentrancyGuard {
|
||||
* @param _proposalId ID предложения
|
||||
* @param _chainId ID цепочки
|
||||
*/
|
||||
function syncToChain(uint256 _proposalId, uint256 _chainId) internal {
|
||||
// Проверяем, что цепочка поддерживается
|
||||
require(supportedChains[_chainId], "Chain not supported");
|
||||
|
||||
// Получаем информацию о предложении
|
||||
Proposal storage proposal = proposals[_proposalId];
|
||||
require(proposal.id == _proposalId, "Proposal does not exist");
|
||||
|
||||
// Проверяем, что цепочка готова к синхронизации
|
||||
require(checkChainConnection(_chainId), "Chain not ready for sync");
|
||||
|
||||
// Создаем Merkle root для синхронизации
|
||||
bytes32 syncData = keccak256(abi.encodePacked(_proposalId, currentChainId, proposal.operation));
|
||||
|
||||
// Обновляем Merkle root для целевой цепочки
|
||||
chainMerkleRoots[_chainId] = syncData;
|
||||
|
||||
// Эмитим событие для cross-chain bridge
|
||||
emit CrossChainExecutionSync(_proposalId, currentChainId, _chainId);
|
||||
}
|
||||
// УДАЛЕНО: syncToChain — не используется в подпись‑ориентированной схеме
|
||||
|
||||
/**
|
||||
* @dev Получить количество поддерживаемых цепочек
|
||||
@@ -465,14 +447,12 @@ contract DLE is ERC20, ReentrancyGuard {
|
||||
* @dev Добавить поддерживаемую цепочку (только для владельцев токенов)
|
||||
* @param _chainId ID цепочки
|
||||
*/
|
||||
function addSupportedChain(uint256 _chainId) external {
|
||||
require(balanceOf(msg.sender) > 0, "Must hold tokens to add chain");
|
||||
// Управление списком сетей теперь выполняется только через предложения
|
||||
function _addSupportedChain(uint256 _chainId) internal {
|
||||
require(!supportedChains[_chainId], "Chain already supported");
|
||||
require(_chainId != currentChainId, "Cannot add current chain");
|
||||
|
||||
supportedChains[_chainId] = true;
|
||||
supportedChainIds.push(_chainId);
|
||||
|
||||
emit ChainAdded(_chainId);
|
||||
}
|
||||
|
||||
@@ -480,13 +460,10 @@ contract DLE is ERC20, ReentrancyGuard {
|
||||
* @dev Удалить поддерживаемую цепочку (только для владельцев токенов)
|
||||
* @param _chainId ID цепочки
|
||||
*/
|
||||
function removeSupportedChain(uint256 _chainId) external {
|
||||
require(balanceOf(msg.sender) > 0, "Must hold tokens to remove chain");
|
||||
function _removeSupportedChain(uint256 _chainId) internal {
|
||||
require(supportedChains[_chainId], "Chain not supported");
|
||||
require(_chainId != currentChainId, "Cannot remove current chain");
|
||||
|
||||
supportedChains[_chainId] = false;
|
||||
|
||||
// Удаляем из массива
|
||||
for (uint256 i = 0; i < supportedChainIds.length; i++) {
|
||||
if (supportedChainIds[i] == _chainId) {
|
||||
@@ -495,10 +472,6 @@ contract DLE is ERC20, ReentrancyGuard {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Очищаем Merkle root для цепочки
|
||||
delete chainMerkleRoots[_chainId];
|
||||
|
||||
emit ChainRemoved(_chainId);
|
||||
}
|
||||
|
||||
@@ -507,22 +480,13 @@ contract DLE is ERC20, ReentrancyGuard {
|
||||
* @param _chainId ID цепочки
|
||||
* @param _merkleRoot Merkle root для цепочки
|
||||
*/
|
||||
function setChainMerkleRoot(uint256 _chainId, bytes32 _merkleRoot) external {
|
||||
require(balanceOf(msg.sender) > 0, "Must hold tokens to set merkle root");
|
||||
require(supportedChains[_chainId], "Chain not supported");
|
||||
|
||||
chainMerkleRoots[_chainId] = _merkleRoot;
|
||||
|
||||
emit ChainMerkleRootSet(_chainId, _merkleRoot);
|
||||
}
|
||||
// УДАЛЕНО: setChainMerkleRoot — небезопасно отдавать любому холдеру
|
||||
|
||||
/**
|
||||
* @dev Получить Merkle root для цепочки
|
||||
* @param _chainId ID цепочки
|
||||
*/
|
||||
function getChainMerkleRoot(uint256 _chainId) external view returns (bytes32) {
|
||||
return chainMerkleRoots[_chainId];
|
||||
}
|
||||
// УДАЛЕНО: getChainMerkleRoot — устарело
|
||||
|
||||
/**
|
||||
* @dev Исполнить операцию
|
||||
@@ -532,19 +496,7 @@ contract DLE is ERC20, ReentrancyGuard {
|
||||
// Декодируем операцию
|
||||
(bytes4 selector, bytes memory data) = abi.decode(_operation, (bytes4, bytes));
|
||||
|
||||
if (selector == bytes4(keccak256("transfer(address,uint256)"))) {
|
||||
// Операция передачи токенов
|
||||
(address to, uint256 amount) = abi.decode(data, (address, uint256));
|
||||
_transfer(msg.sender, to, amount);
|
||||
} else if (selector == bytes4(keccak256("mint(address,uint256)"))) {
|
||||
// Операция минтинга токенов
|
||||
(address to, uint256 amount) = abi.decode(data, (address, uint256));
|
||||
_mint(to, amount);
|
||||
} else if (selector == bytes4(keccak256("burn(address,uint256)"))) {
|
||||
// Операция сжигания токенов
|
||||
(address from, uint256 amount) = abi.decode(data, (address, uint256));
|
||||
_burn(from, amount);
|
||||
} else if (selector == bytes4(keccak256("updateDLEInfo(string,string,string,string,uint256,uint256,string[],uint256)"))) {
|
||||
if (selector == bytes4(keccak256("updateDLEInfo(string,string,string,string,uint256,uint256,string[],uint256)"))) {
|
||||
// Операция обновления информации DLE
|
||||
(string memory name, string memory symbol, string memory location, string memory coordinates,
|
||||
uint256 jurisdiction, uint256 oktmo, string[] memory okvedCodes, uint256 kpp) = abi.decode(data, (string, string, string, string, uint256, uint256, string[], uint256));
|
||||
@@ -565,6 +517,16 @@ contract DLE is ERC20, ReentrancyGuard {
|
||||
// Операция удаления модуля
|
||||
(bytes32 moduleId) = abi.decode(data, (bytes32));
|
||||
_removeModule(moduleId);
|
||||
} else if (selector == bytes4(keccak256("_addSupportedChain(uint256)"))) {
|
||||
(uint256 chainIdToAdd) = abi.decode(data, (uint256));
|
||||
_addSupportedChain(chainIdToAdd);
|
||||
} else if (selector == bytes4(keccak256("_removeSupportedChain(uint256)"))) {
|
||||
(uint256 chainIdToRemove) = abi.decode(data, (uint256));
|
||||
_removeSupportedChain(chainIdToRemove);
|
||||
} else if (selector == bytes4(keccak256("offchainAction(bytes32,string,bytes32)"))) {
|
||||
// Оффчейн операция для приложения: идентификатор, тип, хеш полезной нагрузки
|
||||
// (bytes32 actionId, string memory kind, bytes32 payloadHash) = abi.decode(data, (bytes32, string, bytes32));
|
||||
// Ончейн-побочных эффектов нет. Факт решения фиксируется событием ProposalExecuted.
|
||||
} else {
|
||||
// Неизвестная операция
|
||||
revert("Unknown operation");
|
||||
@@ -654,7 +616,6 @@ contract DLE is ERC20, ReentrancyGuard {
|
||||
uint256 _chainId
|
||||
) external returns (uint256) {
|
||||
require(supportedChains[_chainId], "Chain not supported");
|
||||
require(checkChainConnection(_chainId), "Chain not available");
|
||||
require(_moduleAddress != address(0), "Zero address");
|
||||
require(!activeModules[_moduleId], "Module already exists");
|
||||
require(balanceOf(msg.sender) > 0, "Must hold tokens to create proposal");
|
||||
@@ -693,7 +654,6 @@ contract DLE is ERC20, ReentrancyGuard {
|
||||
uint256 _chainId
|
||||
) external returns (uint256) {
|
||||
require(supportedChains[_chainId], "Chain not supported");
|
||||
require(checkChainConnection(_chainId), "Chain not available");
|
||||
require(activeModules[_moduleId], "Module does not exist");
|
||||
require(balanceOf(msg.sender) > 0, "Must hold tokens to create proposal");
|
||||
|
||||
@@ -782,192 +742,146 @@ contract DLE is ERC20, ReentrancyGuard {
|
||||
return currentChainId;
|
||||
}
|
||||
|
||||
// События для новых функций
|
||||
event SyncCompleted(uint256 proposalId);
|
||||
event DLEDeactivated(address indexed deactivatedBy, uint256 timestamp);
|
||||
event DeactivationProposalCreated(uint256 proposalId, address indexed initiator, string description);
|
||||
event DeactivationProposalVoted(uint256 proposalId, address indexed voter, bool support, uint256 votingPower);
|
||||
event DeactivationProposalExecuted(uint256 proposalId, address indexed executedBy);
|
||||
|
||||
// Структура для предложения деактивации
|
||||
struct DeactivationProposal {
|
||||
uint256 id;
|
||||
string description;
|
||||
uint256 forVotes;
|
||||
uint256 againstVotes;
|
||||
bool executed;
|
||||
uint256 deadline;
|
||||
address initiator;
|
||||
uint256 chainId;
|
||||
mapping(address => bool) hasVoted;
|
||||
}
|
||||
|
||||
// Предложения деактивации
|
||||
mapping(uint256 => DeactivationProposal) public deactivationProposals;
|
||||
uint256 public deactivationProposalCounter;
|
||||
bool public isDeactivated;
|
||||
|
||||
/**
|
||||
* @dev Создать предложение о деактивации DLE
|
||||
* @param _description Описание предложения
|
||||
* @param _duration Длительность голосования в секундах
|
||||
* @param _chainId ID цепочки для деактивации
|
||||
*/
|
||||
function createDeactivationProposal(
|
||||
string memory _description,
|
||||
uint256 _duration,
|
||||
uint256 _chainId
|
||||
) external returns (uint256) {
|
||||
require(!isDeactivated, "DLE already deactivated");
|
||||
require(balanceOf(msg.sender) > 0, "Must hold tokens to create deactivation proposal");
|
||||
require(_duration > 0, "Duration must be positive");
|
||||
require(supportedChains[_chainId], "Chain not supported");
|
||||
|
||||
uint256 proposalId = deactivationProposalCounter++;
|
||||
DeactivationProposal storage proposal = deactivationProposals[proposalId];
|
||||
|
||||
proposal.id = proposalId;
|
||||
proposal.description = _description;
|
||||
proposal.forVotes = 0;
|
||||
proposal.againstVotes = 0;
|
||||
proposal.executed = false;
|
||||
proposal.deadline = block.timestamp + _duration;
|
||||
proposal.initiator = msg.sender;
|
||||
proposal.chainId = _chainId;
|
||||
|
||||
emit DeactivationProposalCreated(proposalId, msg.sender, _description);
|
||||
return proposalId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Голосовать за предложение деактивации
|
||||
* @param _proposalId ID предложения
|
||||
* @param _support Поддержка предложения
|
||||
*/
|
||||
function voteDeactivation(uint256 _proposalId, bool _support) external nonReentrant {
|
||||
DeactivationProposal storage proposal = deactivationProposals[_proposalId];
|
||||
require(proposal.id == _proposalId, "Deactivation proposal does not exist");
|
||||
require(block.timestamp < proposal.deadline, "Voting ended");
|
||||
require(!proposal.executed, "Proposal already executed");
|
||||
require(!proposal.hasVoted[msg.sender], "Already voted");
|
||||
require(balanceOf(msg.sender) > 0, "No tokens to vote");
|
||||
|
||||
uint256 votingPower = balanceOf(msg.sender);
|
||||
|
||||
if (_support) {
|
||||
proposal.forVotes += votingPower;
|
||||
} else {
|
||||
proposal.againstVotes += votingPower;
|
||||
}
|
||||
|
||||
proposal.hasVoted[msg.sender] = true;
|
||||
|
||||
emit DeactivationProposalVoted(_proposalId, msg.sender, _support, votingPower);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Проверить результат предложения деактивации
|
||||
* @param _proposalId ID предложения
|
||||
*/
|
||||
function checkDeactivationProposalResult(uint256 _proposalId) public view returns (bool passed, bool quorumReached) {
|
||||
DeactivationProposal storage proposal = deactivationProposals[_proposalId];
|
||||
require(proposal.id == _proposalId, "Deactivation proposal does not exist");
|
||||
|
||||
uint256 totalVotes = proposal.forVotes + proposal.againstVotes;
|
||||
uint256 totalSupply = totalSupply();
|
||||
|
||||
quorumReached = totalVotes >= (totalSupply * quorumPercentage) / 100;
|
||||
passed = quorumReached && proposal.forVotes > proposal.againstVotes;
|
||||
|
||||
return (passed, quorumReached);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Исполнить предложение деактивации
|
||||
* @param _proposalId ID предложения
|
||||
*/
|
||||
function executeDeactivationProposal(uint256 _proposalId) external {
|
||||
DeactivationProposal storage proposal = deactivationProposals[_proposalId];
|
||||
require(proposal.id == _proposalId, "Deactivation proposal does not exist");
|
||||
require(!proposal.executed, "Proposal already executed");
|
||||
require(block.timestamp >= proposal.deadline, "Voting not ended");
|
||||
|
||||
(bool passed, bool quorumReached) = checkDeactivationProposalResult(_proposalId);
|
||||
require(quorumReached, "Quorum not reached");
|
||||
require(passed, "Proposal not passed");
|
||||
|
||||
proposal.executed = true;
|
||||
isDeactivated = true;
|
||||
dleInfo.isActive = false;
|
||||
|
||||
emit DeactivationProposalExecuted(_proposalId, msg.sender);
|
||||
emit DLEDeactivated(msg.sender, block.timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Деактивировать DLE напрямую (только при достижении кворума)
|
||||
* Может быть вызвана только если есть активное предложение деактивации с достигнутым кворумом
|
||||
*/
|
||||
function deactivate() external {
|
||||
require(!isDeactivated, "DLE already deactivated");
|
||||
require(balanceOf(msg.sender) > 0, "Must hold tokens to deactivate DLE");
|
||||
|
||||
// Проверяем, есть ли активное предложение деактивации с достигнутым кворумом
|
||||
bool hasValidDeactivationProposal = false;
|
||||
|
||||
for (uint256 i = 0; i < deactivationProposalCounter; i++) {
|
||||
DeactivationProposal storage proposal = deactivationProposals[i];
|
||||
if (!proposal.executed && block.timestamp >= proposal.deadline) {
|
||||
(bool passed, bool quorumReached) = checkDeactivationProposalResult(i);
|
||||
if (quorumReached && passed) {
|
||||
hasValidDeactivationProposal = true;
|
||||
proposal.executed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
require(hasValidDeactivationProposal, "No valid deactivation proposal with quorum");
|
||||
|
||||
isDeactivated = true;
|
||||
dleInfo.isActive = false;
|
||||
|
||||
emit DLEDeactivated(msg.sender, block.timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Проверить, деактивирован ли DLE
|
||||
*/
|
||||
function isActive() external view returns (bool) {
|
||||
return !isDeactivated && dleInfo.isActive;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Получить информацию о предложении деактивации
|
||||
* @param _proposalId ID предложения
|
||||
*/
|
||||
function getDeactivationProposal(uint256 _proposalId) external view returns (
|
||||
// ===== Интерфейс аналитики для API =====
|
||||
function getProposalSummary(uint256 _proposalId) external view returns (
|
||||
uint256 id,
|
||||
string memory description,
|
||||
uint256 forVotes,
|
||||
uint256 againstVotes,
|
||||
bool executed,
|
||||
bool canceled,
|
||||
uint256 deadline,
|
||||
address initiator,
|
||||
uint256 chainId
|
||||
uint256 governanceChainId,
|
||||
uint256 timelock,
|
||||
uint256 snapshotTimepoint,
|
||||
uint256[] memory targets
|
||||
) {
|
||||
DeactivationProposal storage proposal = deactivationProposals[_proposalId];
|
||||
require(proposal.id == _proposalId, "Deactivation proposal does not exist");
|
||||
|
||||
Proposal storage p = proposals[_proposalId];
|
||||
require(p.id == _proposalId, "Proposal does not exist");
|
||||
return (
|
||||
proposal.id,
|
||||
proposal.description,
|
||||
proposal.forVotes,
|
||||
proposal.againstVotes,
|
||||
proposal.executed,
|
||||
proposal.deadline,
|
||||
proposal.initiator,
|
||||
proposal.chainId
|
||||
p.id,
|
||||
p.description,
|
||||
p.forVotes,
|
||||
p.againstVotes,
|
||||
p.executed,
|
||||
p.canceled,
|
||||
p.deadline,
|
||||
p.initiator,
|
||||
p.governanceChainId,
|
||||
p.timelock,
|
||||
p.snapshotTimepoint,
|
||||
p.targetChains
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function getGovernanceParams() external view returns (
|
||||
uint256 quorumPct,
|
||||
uint256 chainId,
|
||||
uint256 supportedCount
|
||||
) {
|
||||
return (quorumPercentage, currentChainId, supportedChainIds.length);
|
||||
}
|
||||
|
||||
function listSupportedChains() external view returns (uint256[] memory) {
|
||||
return supportedChainIds;
|
||||
}
|
||||
|
||||
function getVotingPowerAt(address voter, uint256 timepoint) external view returns (uint256) {
|
||||
return getPastVotes(voter, timepoint);
|
||||
}
|
||||
|
||||
// ===== Пагинация и агрегирование =====
|
||||
function getProposalsCount() external view returns (uint256) {
|
||||
return allProposalIds.length;
|
||||
}
|
||||
|
||||
function listProposals(uint256 offset, uint256 limit) external view returns (uint256[] memory) {
|
||||
uint256 total = allProposalIds.length;
|
||||
if (offset >= total) {
|
||||
return new uint256[](0);
|
||||
}
|
||||
uint256 end = offset + limit;
|
||||
if (end > total) end = total;
|
||||
uint256[] memory page = new uint256[](end - offset);
|
||||
for (uint256 i = offset; i < end; i++) {
|
||||
page[i - offset] = allProposalIds[i];
|
||||
}
|
||||
return page;
|
||||
}
|
||||
|
||||
// 0=Pending, 1=Succeeded, 2=Defeated, 3=Executed, 4=Canceled, 5=ReadyForExecution
|
||||
function getProposalState(uint256 _proposalId) public view returns (uint8 state) {
|
||||
Proposal storage p = proposals[_proposalId];
|
||||
require(p.id == _proposalId, "Proposal does not exist");
|
||||
if (p.canceled) return 4;
|
||||
if (p.executed) return 3;
|
||||
(bool passed, bool quorumReached) = checkProposalResult(_proposalId);
|
||||
bool votingOver = block.timestamp >= p.deadline;
|
||||
bool ready = passed && quorumReached && block.timestamp >= p.timelock;
|
||||
if (ready) return 5; // ReadyForExecution
|
||||
if (passed && (votingOver || quorumReached)) return 1; // Succeeded
|
||||
if (votingOver && !passed) return 2; // Defeated
|
||||
return 0; // Pending
|
||||
}
|
||||
|
||||
function getQuorumAt(uint256 timepoint) external view returns (uint256) {
|
||||
uint256 supply = getPastTotalSupply(timepoint);
|
||||
return (supply * quorumPercentage) / 100;
|
||||
}
|
||||
|
||||
function getProposalVotes(uint256 _proposalId) external view returns (
|
||||
uint256 forVotes,
|
||||
uint256 againstVotes,
|
||||
uint256 totalVotes,
|
||||
uint256 quorumRequired
|
||||
) {
|
||||
Proposal storage p = proposals[_proposalId];
|
||||
require(p.id == _proposalId, "Proposal does not exist");
|
||||
uint256 supply = getPastTotalSupply(p.snapshotTimepoint);
|
||||
uint256 quorumReq = (supply * quorumPercentage) / 100;
|
||||
return (p.forVotes, p.againstVotes, p.forVotes + p.againstVotes, quorumReq);
|
||||
}
|
||||
|
||||
// События для новых функций
|
||||
event SyncCompleted(uint256 proposalId);
|
||||
event DLEDeactivated(address indexed deactivatedBy, uint256 timestamp);
|
||||
|
||||
bool public isDeactivated;
|
||||
|
||||
// Деактивация вынесена в отдельный модуль. См. DeactivationModule.
|
||||
function isActive() external view returns (bool) {
|
||||
return !isDeactivated && dleInfo.isActive;
|
||||
}
|
||||
// ===== Вспомогательные функции =====
|
||||
function _isTargetChain(Proposal storage p, uint256 chainId) internal view returns (bool) {
|
||||
for (uint256 i = 0; i < p.targetChains.length; i++) {
|
||||
if (p.targetChains[i] == chainId) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ===== Overrides для ERC20Votes =====
|
||||
function _update(address from, address to, uint256 value)
|
||||
internal
|
||||
override(ERC20, ERC20Votes)
|
||||
{
|
||||
super._update(from, to, value);
|
||||
}
|
||||
|
||||
// Разрешаем неоднозначность nonces из базовых классов
|
||||
function nonces(address owner)
|
||||
public
|
||||
view
|
||||
override(ERC20Permit, Nonces)
|
||||
returns (uint256)
|
||||
{
|
||||
return super.nonces(owner);
|
||||
}
|
||||
|
||||
// Запрет делегирования на третьих лиц: разрешено только делегировать самому себе
|
||||
function _delegate(address delegator, address delegatee) internal override {
|
||||
require(delegator == delegatee, "Delegation disabled");
|
||||
super._delegate(delegator, delegatee);
|
||||
}
|
||||
}
|
||||
|
||||
23
backend/contracts/FactoryDeployer.sol
Normal file
23
backend/contracts/FactoryDeployer.sol
Normal file
@@ -0,0 +1,23 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
contract FactoryDeployer {
|
||||
event Deployed(address addr, bytes32 salt);
|
||||
|
||||
function deploy(bytes32 salt, bytes memory creationCode) external payable returns (address addr) {
|
||||
require(creationCode.length != 0, "init code empty");
|
||||
// solhint-disable-next-line no-inline-assembly
|
||||
assembly {
|
||||
addr := create2(callvalue(), add(creationCode, 0x20), mload(creationCode), salt)
|
||||
}
|
||||
require(addr != address(0), "CREATE2 failed");
|
||||
emit Deployed(addr, salt);
|
||||
}
|
||||
|
||||
function computeAddress(bytes32 salt, bytes32 initCodeHash) external view returns (address) {
|
||||
bytes32 hash = keccak256(abi.encodePacked(bytes1(0xff), address(this), salt, initCodeHash));
|
||||
return address(uint160(uint256(hash)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ const auth = require('../middleware/auth');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const ethers = require('ethers'); // Added ethers for private key validation
|
||||
const create2 = require('../utils/create2');
|
||||
|
||||
/**
|
||||
* @route POST /api/dle-v2
|
||||
@@ -298,4 +299,34 @@ router.post('/validate-private-key', async (req, res, next) => {
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
module.exports = router;
|
||||
|
||||
/**
|
||||
* Дополнительные маршруты (подключаются из app.js)
|
||||
*/
|
||||
|
||||
// Предсказание адресов по выбранным сетям с использованием CREATE2
|
||||
router.post('/predict-addresses', auth.requireAuth, auth.requireAdmin, async (req, res) => {
|
||||
try {
|
||||
const { name, symbol, selectedNetworks } = req.body || {};
|
||||
if (!selectedNetworks || !Array.isArray(selectedNetworks) || selectedNetworks.length === 0) {
|
||||
return res.status(400).json({ success: false, message: 'Не переданы сети' });
|
||||
}
|
||||
|
||||
// Используем служебные секреты для фабрики и SALT
|
||||
// Ожидаем, что на сервере настроены переменные окружения или конфиги на сеть
|
||||
const result = {};
|
||||
for (const chainId of selectedNetworks) {
|
||||
const factory = process.env[`FACTORY_ADDRESS_${chainId}`] || process.env.FACTORY_ADDRESS;
|
||||
const saltHex = process.env[`CREATE2_SALT_${chainId}`] || process.env.CREATE2_SALT;
|
||||
const initCodeHash = process.env[`INIT_CODE_HASH_${chainId}`] || process.env.INIT_CODE_HASH;
|
||||
if (!factory || !saltHex || !initCodeHash) continue;
|
||||
result[chainId] = create2.computeCreate2Address(factory, saltHex, initCodeHash);
|
||||
}
|
||||
|
||||
return res.json({ success: true, data: result });
|
||||
} catch (e) {
|
||||
logger.error('predict-addresses error', e);
|
||||
return res.status(500).json({ success: false, message: 'Ошибка расчета адресов' });
|
||||
}
|
||||
});
|
||||
@@ -1,3 +1,71 @@
|
||||
/* eslint-disable no-console */
|
||||
const hre = require('hardhat');
|
||||
|
||||
async function main() {
|
||||
const { ethers } = hre;
|
||||
const rpcUrl = process.env.RPC_URL;
|
||||
const pk = process.env.PRIVATE_KEY;
|
||||
if (!rpcUrl || !pk) throw new Error('RPC_URL/PRIVATE_KEY required');
|
||||
|
||||
const provider = new ethers.JsonRpcProvider(rpcUrl);
|
||||
const wallet = new ethers.Wallet(pk, provider);
|
||||
|
||||
const salt = process.env.CREATE2_SALT;
|
||||
const initCodeHash = process.env.INIT_CODE_HASH;
|
||||
let factoryAddress = process.env.FACTORY_ADDRESS;
|
||||
|
||||
if (!salt || !initCodeHash) throw new Error('CREATE2_SALT/INIT_CODE_HASH required');
|
||||
|
||||
// Ensure factory
|
||||
if (!factoryAddress) {
|
||||
const Factory = await hre.ethers.getContractFactory('FactoryDeployer', wallet);
|
||||
const factory = await Factory.deploy();
|
||||
await factory.waitForDeployment();
|
||||
factoryAddress = await factory.getAddress();
|
||||
} else {
|
||||
const code = await provider.getCode(factoryAddress);
|
||||
if (code === '0x') {
|
||||
const Factory = await hre.ethers.getContractFactory('FactoryDeployer', wallet);
|
||||
const factory = await Factory.deploy();
|
||||
await factory.waitForDeployment();
|
||||
factoryAddress = await factory.getAddress();
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare DLE init code = creation bytecode WITH constructor args
|
||||
const DLE = await hre.ethers.getContractFactory('DLE', wallet);
|
||||
const paramsPath = require('path').join(__dirname, './current-params.json');
|
||||
const params = require(paramsPath);
|
||||
const dleConfig = {
|
||||
name: params.name,
|
||||
symbol: params.symbol,
|
||||
location: params.location,
|
||||
coordinates: params.coordinates,
|
||||
jurisdiction: params.jurisdiction,
|
||||
oktmo: params.oktmo,
|
||||
okvedCodes: params.okvedCodes || [],
|
||||
kpp: params.kpp,
|
||||
quorumPercentage: params.quorumPercentage,
|
||||
initialPartners: params.initialPartners,
|
||||
initialAmounts: params.initialAmounts,
|
||||
supportedChainIds: params.supportedChainIds
|
||||
};
|
||||
const deployTx = await DLE.getDeployTransaction(dleConfig, params.currentChainId);
|
||||
const dleInit = deployTx.data; // полноценный init code
|
||||
|
||||
// Deploy via factory
|
||||
const Factory = await hre.ethers.getContractAt('FactoryDeployer', factoryAddress, wallet);
|
||||
const tx = await Factory.deploy(salt, dleInit);
|
||||
const rc = await tx.wait();
|
||||
const addr = rc.logs?.[0]?.args?.addr || (await Factory.computeAddress(salt, initCodeHash));
|
||||
console.log('DLE v2 задеплоен по адресу:', addr);
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
/**
|
||||
* Copyright (c) 2024-2025 Тарабанов Александр Викторович
|
||||
* All rights reserved.
|
||||
|
||||
69
backend/scripts/deploy/deploy-multichain.js
Normal file
69
backend/scripts/deploy/deploy-multichain.js
Normal file
@@ -0,0 +1,69 @@
|
||||
/* eslint-disable no-console */
|
||||
const hre = require('hardhat');
|
||||
const path = require('path');
|
||||
|
||||
async function deployInNetwork(rpcUrl, pk, salt, initCodeHash, factoryAddress, dleInit) {
|
||||
const { ethers } = hre;
|
||||
const provider = new ethers.JsonRpcProvider(rpcUrl);
|
||||
const wallet = new ethers.Wallet(pk, provider);
|
||||
|
||||
// Ensure factory
|
||||
let faddr = factoryAddress;
|
||||
const code = faddr ? await provider.getCode(faddr) : '0x';
|
||||
if (!faddr || code === '0x') {
|
||||
const Factory = await hre.ethers.getContractFactory('FactoryDeployer', wallet);
|
||||
const factory = await Factory.deploy();
|
||||
await factory.waitForDeployment();
|
||||
faddr = await factory.getAddress();
|
||||
}
|
||||
const Factory = await hre.ethers.getContractAt('FactoryDeployer', faddr, wallet);
|
||||
const tx = await Factory.deploy(salt, dleInit);
|
||||
const rc = await tx.wait();
|
||||
const addr = rc.logs?.[0]?.args?.addr || (await Factory.computeAddress(salt, initCodeHash));
|
||||
return { factory: faddr, address: addr };
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const { ethers } = hre;
|
||||
const pk = process.env.PRIVATE_KEY;
|
||||
const salt = process.env.CREATE2_SALT;
|
||||
const initCodeHash = process.env.INIT_CODE_HASH;
|
||||
const networks = (process.env.MULTICHAIN_RPC_URLS || '').split(',').map(s => s.trim()).filter(Boolean);
|
||||
const factories = (process.env.MULTICHAIN_FACTORY_ADDRESSES || '').split(',').map(s => s.trim());
|
||||
|
||||
if (!pk || !salt || !initCodeHash || networks.length === 0) throw new Error('Env: PRIVATE_KEY, CREATE2_SALT, INIT_CODE_HASH, MULTICHAIN_RPC_URLS');
|
||||
|
||||
// Prepare init code once
|
||||
const paramsPath = path.join(__dirname, './current-params.json');
|
||||
const params = require(paramsPath);
|
||||
const DLE = await hre.ethers.getContractFactory('DLE');
|
||||
const dleConfig = {
|
||||
name: params.name,
|
||||
symbol: params.symbol,
|
||||
location: params.location,
|
||||
coordinates: params.coordinates,
|
||||
jurisdiction: params.jurisdiction,
|
||||
oktmo: params.oktmo,
|
||||
okvedCodes: params.okvedCodes || [],
|
||||
kpp: params.kpp,
|
||||
quorumPercentage: params.quorumPercentage,
|
||||
initialPartners: params.initialPartners,
|
||||
initialAmounts: params.initialAmounts,
|
||||
supportedChainIds: params.supportedChainIds
|
||||
};
|
||||
const deployTx = await DLE.getDeployTransaction(dleConfig, params.currentChainId);
|
||||
const dleInit = deployTx.data;
|
||||
|
||||
const results = [];
|
||||
for (let i = 0; i < networks.length; i++) {
|
||||
const rpcUrl = networks[i];
|
||||
const factory = factories[i] || process.env.FACTORY_ADDRESS || null;
|
||||
const r = await deployInNetwork(rpcUrl, pk, salt, initCodeHash, factory, dleInit);
|
||||
results.push({ rpcUrl, ...r });
|
||||
}
|
||||
console.log('MULTICHAIN_DEPLOY_RESULT', JSON.stringify(results));
|
||||
}
|
||||
|
||||
main().catch((e) => { console.error(e); process.exit(1); });
|
||||
|
||||
|
||||
@@ -49,59 +49,48 @@ class DLEV2Service {
|
||||
fs.copyFileSync(paramsFile, tempParamsFile);
|
||||
logger.info(`Файл параметров скопирован успешно`);
|
||||
|
||||
// Определяем сеть для деплоя (берем первую из выбранных сетей)
|
||||
const chainId = deployParams.supportedChainIds && deployParams.supportedChainIds.length > 0
|
||||
? deployParams.supportedChainIds[0]
|
||||
: 1; // По умолчанию Ethereum
|
||||
|
||||
// Получаем rpc_url из базы по chain_id
|
||||
logger.info(`Поиск RPC URL для chain_id: ${chainId}`);
|
||||
const rpcUrl = await getRpcUrlByChainId(chainId);
|
||||
if (!rpcUrl) {
|
||||
logger.error(`RPC URL для сети с chain_id ${chainId} не найден в базе данных`);
|
||||
throw new Error(`RPC URL для сети с chain_id ${chainId} не найден в базе данных`);
|
||||
// Готовим RPC для всех выбранных сетей
|
||||
const rpcUrls = [];
|
||||
for (const cid of deployParams.supportedChainIds) {
|
||||
logger.info(`Поиск RPC URL для chain_id: ${cid}`);
|
||||
const ru = await getRpcUrlByChainId(cid);
|
||||
if (!ru) {
|
||||
throw new Error(`RPC URL для сети с chain_id ${cid} не найден в базе данных`);
|
||||
}
|
||||
rpcUrls.push(ru);
|
||||
}
|
||||
logger.info(`Найден RPC URL для chain_id ${chainId}: ${rpcUrl}`);
|
||||
|
||||
// Проверяем баланс кошелька
|
||||
const { ethers } = require('ethers');
|
||||
const provider = new ethers.JsonRpcProvider(rpcUrl);
|
||||
const walletAddress = dleParams.privateKey ? new ethers.Wallet(dleParams.privateKey, provider).address : null;
|
||||
|
||||
if (walletAddress) {
|
||||
const balance = await provider.getBalance(walletAddress);
|
||||
const minBalance = ethers.parseEther("0.00001"); // Временно уменьшено для тестирования
|
||||
logger.info(`Баланс кошелька ${walletAddress}: ${ethers.formatEther(balance)} ETH`);
|
||||
|
||||
if (balance < minBalance) {
|
||||
logger.warn(`Недостаточно ETH для деплоя. Баланс: ${ethers.formatEther(balance)} ETH, требуется минимум: ${ethers.formatEther(minBalance)} ETH`);
|
||||
throw new Error(`Недостаточно ETH для деплоя. Баланс: ${ethers.formatEther(balance)} ETH, требуется минимум: ${ethers.formatEther(minBalance)} ETH. Пополните кошелек через Sepolia faucet: https://sepoliafaucet.com/`);
|
||||
|
||||
// Лёгкая проверка баланса в первой сети
|
||||
{
|
||||
const { ethers } = require('ethers');
|
||||
const provider = new ethers.JsonRpcProvider(rpcUrls[0]);
|
||||
const walletAddress = dleParams.privateKey ? new ethers.Wallet(dleParams.privateKey, provider).address : null;
|
||||
if (walletAddress) {
|
||||
const balance = await provider.getBalance(walletAddress);
|
||||
const minBalance = ethers.parseEther("0.00001");
|
||||
logger.info(`Баланс кошелька ${walletAddress}: ${ethers.formatEther(balance)} ETH`);
|
||||
if (balance < minBalance) {
|
||||
throw new Error(`Недостаточно ETH для деплоя в ${deployParams.supportedChainIds[0]}. Баланс: ${ethers.formatEther(balance)} ETH`);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!dleParams.privateKey) {
|
||||
throw new Error('Приватный ключ для деплоя не передан');
|
||||
}
|
||||
|
||||
// Маппинг chain_id к именам сетей в Hardhat
|
||||
const chainIdToNetworkName = {
|
||||
1: 'ethereum',
|
||||
137: 'polygon',
|
||||
56: 'bsc',
|
||||
42161: 'arbitrum',
|
||||
11155111: 'sepolia'
|
||||
};
|
||||
|
||||
const networkName = chainIdToNetworkName[chainId];
|
||||
if (!networkName) {
|
||||
throw new Error(`Сеть с chain_id ${chainId} не поддерживается для деплоя`);
|
||||
}
|
||||
// Рассчитываем INIT_CODE_HASH автоматически из актуального initCode
|
||||
const initCodeHash = await this.computeInitCodeHash(deployParams);
|
||||
|
||||
// Запускаем скрипт деплоя с нужными переменными окружения
|
||||
const result = await this.runDeployScript(paramsFile, {
|
||||
rpcUrl,
|
||||
// Собираем адреса фабрик по сетям (если есть)
|
||||
const factoryAddresses = deployParams.supportedChainIds.map(cid => process.env[`FACTORY_ADDRESS_${cid}`] || '').join(',');
|
||||
|
||||
// Мультисетевой деплой одним вызовом
|
||||
const result = await this.runDeployMultichain(paramsFile, {
|
||||
rpcUrls: rpcUrls.join(','),
|
||||
privateKey: dleParams.privateKey,
|
||||
networkId: networkName,
|
||||
envNetworkKey: chainId.toString().toUpperCase()
|
||||
salt: process.env.CREATE2_SALT,
|
||||
initCodeHash,
|
||||
factories: factoryAddresses
|
||||
});
|
||||
|
||||
// Очищаем временные файлы
|
||||
@@ -237,14 +226,12 @@ class DLEV2Service {
|
||||
return;
|
||||
}
|
||||
|
||||
// Формируем переменные окружения для скрипта деплоя
|
||||
const envVars = {
|
||||
...process.env,
|
||||
RPC_URL: extraEnv.rpcUrl,
|
||||
PRIVATE_KEY: extraEnv.privateKey
|
||||
};
|
||||
|
||||
// Запускаем скрипт без указания сети, передаем RPC URL и приватный ключ через переменные окружения
|
||||
const hardhatProcess = spawn('npx', ['hardhat', 'run', scriptPath], {
|
||||
cwd: path.join(__dirname, '..'),
|
||||
env: envVars,
|
||||
@@ -266,7 +253,6 @@ class DLEV2Service {
|
||||
|
||||
hardhatProcess.on('close', (code) => {
|
||||
try {
|
||||
// Пытаемся извлечь результат из stdout независимо от кода завершения
|
||||
const result = this.extractDeployResult(stdout);
|
||||
resolve(result);
|
||||
} catch (error) {
|
||||
@@ -286,6 +272,40 @@ class DLEV2Service {
|
||||
});
|
||||
}
|
||||
|
||||
// Мультисетевой деплой
|
||||
runDeployMultichain(paramsFile, opts = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const scriptPath = path.join(__dirname, '../scripts/deploy/deploy-multichain.js');
|
||||
if (!fs.existsSync(scriptPath)) return reject(new Error('Скрипт мультисетевого деплоя не найден'));
|
||||
const envVars = {
|
||||
...process.env,
|
||||
PRIVATE_KEY: opts.privateKey,
|
||||
CREATE2_SALT: opts.salt,
|
||||
INIT_CODE_HASH: opts.initCodeHash,
|
||||
MULTICHAIN_RPC_URLS: opts.rpcUrls,
|
||||
MULTICHAIN_FACTORY_ADDRESSES: opts.factories || ''
|
||||
};
|
||||
const p = spawn('npx', ['hardhat', 'run', scriptPath], { cwd: path.join(__dirname, '..'), env: envVars, stdio: 'pipe' });
|
||||
let stdout = '', stderr = '';
|
||||
p.stdout.on('data', (d) => { stdout += d.toString(); logger.info(`[MULTI] ${d.toString().trim()}`); });
|
||||
p.stderr.on('data', (d) => { stderr += d.toString(); logger.error(`[MULTI_ERR] ${d.toString().trim()}`); });
|
||||
p.on('close', () => {
|
||||
try {
|
||||
const m = stdout.match(/MULTICHAIN_DEPLOY_RESULT\s*(\[.*\])/s);
|
||||
if (!m) throw new Error('Результат не найден');
|
||||
const arr = JSON.parse(m[1]);
|
||||
const addr = arr[0].address;
|
||||
const allSame = arr.every(x => x.address.toLowerCase() === addr.toLowerCase());
|
||||
if (!allSame) throw new Error('Адреса отличаются между сетями');
|
||||
resolve({ success: true, data: { dleAddress: addr, networks: arr } });
|
||||
} catch (e) {
|
||||
reject(new Error(`Ошибка мультисетевого деплоя: ${e.message}\nSTDOUT:${stdout}\nSTDERR:${stderr}`));
|
||||
}
|
||||
});
|
||||
p.on('error', (e) => reject(e));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Извлекает результат деплоя из stdout
|
||||
* @param {string} stdout - Вывод скрипта
|
||||
@@ -358,6 +378,30 @@ class DLEV2Service {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Авто-расчёт INIT_CODE_HASH
|
||||
async computeInitCodeHash(params) {
|
||||
const hre = require('hardhat');
|
||||
const { ethers } = hre;
|
||||
const DLE = await hre.ethers.getContractFactory('DLE');
|
||||
const dleConfig = {
|
||||
name: params.name,
|
||||
symbol: params.symbol,
|
||||
location: params.location,
|
||||
coordinates: params.coordinates,
|
||||
jurisdiction: params.jurisdiction,
|
||||
oktmo: params.oktmo,
|
||||
okvedCodes: params.okvedCodes || [],
|
||||
kpp: params.kpp,
|
||||
quorumPercentage: params.quorumPercentage,
|
||||
initialPartners: params.initialPartners,
|
||||
initialAmounts: params.initialAmounts,
|
||||
supportedChainIds: params.supportedChainIds
|
||||
};
|
||||
const deployTx = await DLE.getDeployTransaction(dleConfig, params.currentChainId);
|
||||
const initCode = deployTx.data;
|
||||
return ethers.keccak256(initCode);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new DLEV2Service();
|
||||
@@ -416,8 +416,9 @@ async function getBot() {
|
||||
if (recentMessages && recentMessages.length > 0) {
|
||||
// Преобразуем сообщения в формат для AI
|
||||
history = recentMessages.reverse().map(msg => ({
|
||||
role: msg.sender_type === 'user' ? 'user' : 'assistant',
|
||||
content: msg.content || '' // content уже расшифрован encryptedDb
|
||||
// Любые человеческие роли трактуем как 'user', только ответы ассистента — 'assistant'
|
||||
role: msg.sender_type === 'assistant' ? 'assistant' : 'user',
|
||||
content: msg.content || ''
|
||||
}));
|
||||
}
|
||||
} catch (historyError) {
|
||||
@@ -465,7 +466,7 @@ async function getBot() {
|
||||
sender_type: 'assistant',
|
||||
content: aiResponse,
|
||||
channel: 'telegram',
|
||||
role: role,
|
||||
role: 'assistant',
|
||||
direction: 'out',
|
||||
created_at: new Date()
|
||||
});
|
||||
|
||||
21
backend/utils/create2.js
Normal file
21
backend/utils/create2.js
Normal file
@@ -0,0 +1,21 @@
|
||||
const { keccak256, getAddress } = require('ethers').utils || require('ethers');
|
||||
|
||||
function toBytes(hex) {
|
||||
if (hex.startsWith('0x')) return Buffer.from(hex.slice(2), 'hex');
|
||||
return Buffer.from(hex, 'hex');
|
||||
}
|
||||
|
||||
function computeCreate2Address(factory, saltHex, initCodeHash) {
|
||||
const parts = [
|
||||
'0xff',
|
||||
factory.toLowerCase(),
|
||||
saltHex.toLowerCase(),
|
||||
initCodeHash.toLowerCase()
|
||||
].map(x => x.startsWith('0x') ? x.slice(2) : x).join('');
|
||||
const hash = '0x' + require('crypto').createHash('sha3-256').update(Buffer.from(parts, 'hex')).digest('hex');
|
||||
return '0x' + hash.slice(-40);
|
||||
}
|
||||
|
||||
module.exports = { computeCreate2Address };
|
||||
|
||||
|
||||
@@ -10,6 +10,18 @@
|
||||
GitHub: https://github.com/HB3-ACCELERATOR
|
||||
-->
|
||||
|
||||
# DLE v2 — краткие обновления
|
||||
|
||||
- Single‑Chain Governance: голосование фиксируется в одной сети, исполнение в целевых сетях по EIP‑712 подписям без внешних мостов.
|
||||
- Снапшоты голосующей силы: `ERC20Votes` (`getPastVotes`, `getPastTotalSupply`) исключают перелив голосов.
|
||||
- Делегирование «только на себя»: 1 токен = 1 голос, запрет делегирования третьим лицам.
|
||||
- Модульность: казна, таймлок, деактивация, коммуникации выделены в отдельные модули, операции выполняются через ядро DLE.
|
||||
- «100% или ничего»: много-сетевые операции исполняются только при готовности всех целевых сетей.
|
||||
- Детерминированный деплой: `FactoryDeployer` + CREATE2 для одинаковых адресов во всех выбранных сетях; INIT_CODE_HASH рассчитывается автоматически из актуального initCode.
|
||||
- Аналитика: добавлены view‑функции для сводок, пагинации и агрегирования по предложениям.
|
||||
|
||||
---
|
||||
|
||||
# DLE - Единый Смарт-Контракт с Модульной Архитектурой
|
||||
|
||||
## 🎯 ПОЛНОЕ ПОНИМАНИЕ ЗАДАЧИ DLE
|
||||
@@ -51,12 +63,12 @@ DLE (Digital Legal Entity) = Универсальная цифровая юри
|
||||
|
||||
### **3. СИСТЕМА УПРАВЛЕНИЯ:**
|
||||
|
||||
#### **Голосование и мультиподпись:**
|
||||
#### **Голосование токен‑холдеров:**
|
||||
```
|
||||
- Только токен-холдеры участвуют в управлении
|
||||
- Каждый токен = одна голосующая сила
|
||||
- Кворум настраиваемый (например, 60% от общего количества токенов)
|
||||
- Мультиподпись через токен-холдеров (проверка баланса при каждой операции)
|
||||
- Коллективное голосование токен‑холдеров (ERC20Votes снапшоты)
|
||||
```
|
||||
|
||||
#### **Создание предложений:**
|
||||
@@ -101,7 +113,6 @@ DLE (Digital Legal Entity) = Универсальная цифровая юри
|
||||
```
|
||||
- ERC-20 токены
|
||||
- Система голосования
|
||||
- Мультиподпись
|
||||
- Мультичейн синхронизация
|
||||
- Управление модулями
|
||||
- DLEInfo (юридическая информация)
|
||||
@@ -141,7 +152,7 @@ DLE (Digital Legal Entity) = Универсальная цифровая юри
|
||||
```
|
||||
- Только токен-холдеры управляют
|
||||
- Проверка баланса при каждой операции
|
||||
- Кворум мультиподписей
|
||||
- Кворум голосов - все решения через коллективное голосование
|
||||
- Синхронизация между цепочками
|
||||
```
|
||||
|
||||
@@ -174,7 +185,7 @@ DLE.sol (Основной контракт) + Модули (добавляемы
|
||||
1. **Один основной контракт** - управление токенами, голосованием, мультиподписью
|
||||
2. **Модули** - специализированные функции (казначейство, иерархическое голосование, коммуникации)
|
||||
3. **Только токен-холдеры** - никаких админских ролей
|
||||
4. **Кворум мультиподписей** - все решения через коллективное голосование
|
||||
4. **Кворум голосов** - все решения через коллективное голосование
|
||||
5. **Проверка баланса** - при каждой операции
|
||||
|
||||
---
|
||||
@@ -190,7 +201,7 @@ DLE.sol
|
||||
├── Выбор цепочки для кворума (governanceChainId)
|
||||
├── Синхронизация голосов между цепочками
|
||||
├── Поддержка множественных цепочек
|
||||
├── Мультиподпись (через токен-холдеров)
|
||||
├── Голосование токен‑холдеров
|
||||
├── Мультичейн синхронизация
|
||||
└── Система модулей (добавление/управление)
|
||||
```
|
||||
@@ -216,7 +227,7 @@ DLE.sol
|
||||
- **Выбор цепочки для кворума** - токен-холдер может выбрать любую поддерживаемую цепочку
|
||||
- **Синхронизация голосов** - после голосования результаты синхронизируются между цепочками
|
||||
- **Поддержка множественных цепочек** - Ethereum, Polygon, BSC и др.
|
||||
- **Мультиподпись** - через токен-холдеров с проверкой баланса
|
||||
- **Голосование** - через токен‑холдеров с проверкой баланса
|
||||
- **Мультичейн синхронизация** - одинаковый адрес во всех цепочках
|
||||
- **Управление модулями** - добавление/удаление через голосование
|
||||
|
||||
@@ -241,14 +252,14 @@ DLE.sol
|
||||
### 5. ExternalDLEModule.sol ✅
|
||||
- **Меж-DLE взаимодействие** - управление DLE B через приложение DLE A
|
||||
- **Встраивание интерфейсов** - безопасное управление
|
||||
- **Проверка прав** - через мультиподпись
|
||||
- **Проверка прав** - через голосование токен‑холдеров
|
||||
- **Аудит действий** - отслеживание операций
|
||||
|
||||
### 6. Мульти-чейн архитектура ✅
|
||||
- **CREATE2 деплой** - одинаковый адрес во всех цепочках
|
||||
- **Синхронизация состояния** - токены, предложения, голосования
|
||||
- **Создание предложений** - в любой цепочке
|
||||
- **Голосование** - в любой цепочке с синхронизацией
|
||||
- **Голосование** - через токен‑холдеров с проверкой баланса
|
||||
|
||||
---
|
||||
|
||||
@@ -257,7 +268,7 @@ DLE.sol
|
||||
### Основные принципы безопасности:
|
||||
1. **Только токен-холдеры** - никаких админских ролей
|
||||
2. **Проверка баланса** - при каждой операции
|
||||
3. **Кворум мультиподписей** - все решения коллективные
|
||||
3. **Кворум голосов** - все решения коллективные
|
||||
4. **Простая логика** - минимум уязвимостей
|
||||
|
||||
### Защита от атак:
|
||||
@@ -503,13 +514,10 @@ function createProposal(
|
||||
uint256 _governanceChainId
|
||||
) external returns (uint256);
|
||||
|
||||
// Синхронизация голосов между цепочками
|
||||
function syncVoteFromChain(
|
||||
uint256 _proposalId,
|
||||
uint256 _fromChainId,
|
||||
uint256 _forVotes,
|
||||
uint256 _againstVotes,
|
||||
bytes memory _proof
|
||||
// Исполнение в целевых сетях по EIP-712 подписям (без мостов)
|
||||
function executeProposalBySignatures(
|
||||
uint256 proposalId,
|
||||
bytes[] calldata signatures
|
||||
) external;
|
||||
|
||||
// Проверка поддерживаемых цепочек
|
||||
@@ -517,29 +525,8 @@ function isChainSupported(uint256 _chainId) external view returns (bool);
|
||||
```
|
||||
|
||||
### Синхронизация между цепочками
|
||||
```solidity
|
||||
// Синхронизация токенов
|
||||
function syncTokenBalance(
|
||||
address holder,
|
||||
uint256 balance,
|
||||
uint256 fromChainId
|
||||
) external;
|
||||
|
||||
// Синхронизация предложений
|
||||
function syncProposal(
|
||||
uint256 proposalId,
|
||||
Proposal memory proposal,
|
||||
uint256 fromChainId
|
||||
) external;
|
||||
|
||||
// Синхронизация голосов
|
||||
function syncVote(
|
||||
uint256 proposalId,
|
||||
address voter,
|
||||
bool support,
|
||||
uint256 fromChainId
|
||||
) external;
|
||||
```
|
||||
- Результаты голосования фиксируются снапшотами ERC20Votes в governance‑сети.
|
||||
- Целевые сети принимают исполнение при верификации EIP‑712 подписей холдеров и кворума на зафиксированном timepoint.
|
||||
|
||||
---
|
||||
|
||||
@@ -940,7 +927,7 @@ contract DLE is ERC20, ReentrancyGuard {
|
||||
|
||||
### ✅ Безопасность
|
||||
- Никаких админских ролей
|
||||
- Простая логика мультиподписи
|
||||
- Простая логика коллективного голосования
|
||||
- Защита от основных атак
|
||||
- Прозрачность всех операций
|
||||
|
||||
@@ -962,7 +949,7 @@ contract DLE is ERC20, ReentrancyGuard {
|
||||
|
||||
**DLE - это единый смарт-контракт с модульной архитектурой, который:**
|
||||
|
||||
1. **Управляется только токен-холдерами** через кворум мультиподписей
|
||||
1. **Управляется только токен‑холдерами** через кворум голосов
|
||||
2. **Проверяет баланс токенов** при каждой операции
|
||||
3. **Использует модули** для специализированных функций
|
||||
4. **Синхронизируется между цепочками** с одинаковым адресом
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
### Архитектурные требования
|
||||
- **Single-Chain Governance**: Голосование происходит только в одной выбранной сети
|
||||
- **Мультиподпись токен-холдеров**: Все операции требуют кворума подписей
|
||||
- **Кворум голосов токен‑холдеров**: Все операции требуют достижения кворума голосующей силы по снапшотам
|
||||
- **Настраиваемые таймлоки**: Инициатор устанавливает задержку для каждого предложения
|
||||
- **Cross-chain исполнение**: Решения выполняются во всех целевых сетях
|
||||
- **Без админских ролей**: Только коллективное управление через токен-холдеров
|
||||
@@ -24,11 +24,23 @@
|
||||
### Технический стек
|
||||
- **Frontend**: Vue.js 3 + Composition API
|
||||
- **Web3**: ethers.js или web3.js
|
||||
- **Контракты**: Solidity + OpenZeppelin + ERC-4337
|
||||
- **Контракты**: Solidity + OpenZeppelin (ERC‑4337 опционально для кошельков/UX)
|
||||
- **Стили**: Scoped CSS с переменными
|
||||
|
||||
---
|
||||
|
||||
## Обновления (DLE v2)
|
||||
|
||||
- Деплой:
|
||||
- Мультисетевой деплой одной кнопкой: backend вызывает `deploy-multichain.js`.
|
||||
- Предсказанные адреса DLE отображаются автоматически (endpoint `/api/dle-v2/predict-addresses`).
|
||||
- INIT_CODE_HASH вычисляется автоматически на backend, не вводится вручную.
|
||||
- Предложения (UI):
|
||||
- Порядок секций: Базовая информация → Timelock → Governance‑сеть → Целевые сети → Тип операции и параметры → Предпросмотр.
|
||||
- Поля: `timelockHours`, `targetChains`, `governanceChainId`.
|
||||
- Аналитика:
|
||||
- Использовать новые view‑функции: `getProposalSummary`, `getProposalState`, `getProposalVotes`, `getQuorumAt`, `getVotingPowerAt`, `listSupportedChains`, `getGovernanceParams`.
|
||||
|
||||
## 1. БЛОК "ПРЕДЛОЖЕНИЯ" (`/management/proposals`)
|
||||
|
||||
### Задача 1.1: Создание предложений
|
||||
|
||||
@@ -105,4 +105,19 @@ contract GovernanceModule {
|
||||
- Проверяйте права доступа
|
||||
- Тестируйте граничные случаи
|
||||
- Валидируйте входные параметры
|
||||
- Проверяйте обработку ошибок
|
||||
- Проверяйте обработку ошибок
|
||||
|
||||
# Модульная архитектура (обновление для DLE v2)
|
||||
|
||||
- Модули выносятся в отдельные контракты: `TreasuryModule`, `TimelockModule`, `DeactivationModule`, `CommunicationModule`.
|
||||
- Подключение/отключение модулей — строго через предложения DLE (`ModuleAdded`/`ModuleRemoved`).
|
||||
- Исполнение модульных операций инициируется основным DLE через `_executeOperation` по безопасному `operationCalldata`.
|
||||
- Денежные переводы из ядра исключены: все токено‑операции внутри `TreasuryModule`.
|
||||
- Таймлок применяется на уровне предложения: `timelockHours` хранится в `Proposal` и проверяется при исполнении.
|
||||
- Для оффчейн действий ядро эмитит событие `OffchainAction`, которое подписывает и обрабатывает бекенд/клиент.
|
||||
|
||||
Последовательность для казначейской операции:
|
||||
1) Создание предложения с типом операции и параметрами, указание `governanceChainId`, `targetChains`, `timelockHours`.
|
||||
2) Сбор голосов в выбранной сети (снапшоты ERC20Votes).
|
||||
3) По наступлению timelock — `executeProposalBySignatures` в целевых сетях с проверкой EIP‑712 подписей и «100% или ничего».
|
||||
4) Ядро вызывает `TreasuryModule` по `abi.encodeWithSelector(...)`.
|
||||
@@ -14,8 +14,64 @@
|
||||
|
||||
## Основной смарт контракт DLE
|
||||
|
||||
### DLE v2: ключевые изменения и API (актуально)
|
||||
- Безопасность: удалены уязвимые Merkle‑механизмы cross‑chain; нет внешних мостов/оракулов.
|
||||
- Голосующая сила: OpenZeppelin `ERC20Votes` (снимки `getPastVotes`, `getPastTotalSupply`).
|
||||
- Делегирование: жестко ограничено «только на себя»; третьим лицам делегировать нельзя (1 токен = 1 голос).
|
||||
- Single‑Chain Governance: голосование происходит в одной выбранной сети (`governanceChainId`), время снапшота фиксируется на создании предложения и используется во всех сетях.
|
||||
- Multi‑Chain исполнение: выполнение в целевых сетях по EIP‑712 подписям холдеров, проверяется суммарная голосующая сила на зафиксированном `timepoint` (без доверия к мостам).
|
||||
- «100% или ничего»: операции считаются успешными только при готовности/успешности всех целевых сетей.
|
||||
- Модули вынесены отдельно: `Treasury`, `Timelock`, `Deactivation`, `Communication` и др. Управление только через предложения.
|
||||
- Детерминированные адреса: фабрика `FactoryDeployer` + CREATE2. Единый адрес DLE и модулей во всех выбранных сетях. INIT_CODE_HASH автоподставляется из актуального initCode.
|
||||
- Аналитика: добавлены view‑функции для агрегирования и пагинации.
|
||||
|
||||
Пример основных функций DLE v2 (интерфейс):
|
||||
```solidity
|
||||
// Создание предложения с фиксацией сети голосования, целевых сетей и таймлока
|
||||
function createProposal(
|
||||
string calldata description,
|
||||
uint256 governanceChainId,
|
||||
uint256[] calldata targetChains,
|
||||
uint64 timelockHours,
|
||||
bytes calldata operationCalldata
|
||||
) external returns (uint256 proposalId);
|
||||
|
||||
// Голосование с использованием снапшотов ERC20Votes (учет силы на момент создания)
|
||||
function vote(uint256 proposalId, bool support) external;
|
||||
|
||||
// Отмена инициатором при наличии достаточной голосующей силы (мягкая отмена)
|
||||
function cancelProposal(uint256 proposalId) external;
|
||||
|
||||
// Исполнение в целевой сети по EIP-712 подписям холдеров (без мостов)
|
||||
function executeProposalBySignatures(
|
||||
uint256 proposalId,
|
||||
bytes[] calldata signatures
|
||||
) external;
|
||||
|
||||
// Просмотровые функции (аналитика)
|
||||
function getProposalState(uint256 proposalId) external view returns (uint8);
|
||||
function getProposalVotes(uint256 proposalId) external view returns (uint256 forVotes, uint256 againstVotes);
|
||||
function getQuorumAt(uint256 timepoint) external view returns (uint256);
|
||||
function getVotingPowerAt(address account, uint256 timepoint) external view returns (uint256);
|
||||
function getProposalSummary(uint256 proposalId) external view returns (/* агрегированные поля */);
|
||||
function getGovernanceParams() external view returns (/* кворум, снапшоты, chainIds */);
|
||||
function listSupportedChains() external view returns (uint256[] memory);
|
||||
```
|
||||
|
||||
События (ключевые):
|
||||
- `ProposalCreated`, `ProposalCancelled`, `ProposalExecuted`
|
||||
- `OffchainAction` (триггер оффчейн‑действий через события)
|
||||
- `ModuleAdded`, `ModuleRemoved`
|
||||
|
||||
Замечания по безопасности:
|
||||
- Снапшоты голосующей силы через `ERC20Votes` исключают перелив голосов.
|
||||
- Верификация EIP‑712 подписей исключает зависимость от внешних мостов.
|
||||
- Отсутствуют админ‑роли: все изменения только предложением и кворумом.
|
||||
- Защита от повторов: `nonces` и EIP‑712 схемы подписи используются по стандарту OZ.
|
||||
```
|
||||
|
||||
### Концепция
|
||||
**Один смарт-контракт** с ERC-20 токенами, настраиваемым кворумом, мультиподписью и модулями. Адрес контракта одновременно выполняет функции банковского счета и контактных данных.
|
||||
**Один смарт-контракт** с ERC-20 токенами, настраиваемым кворумом и модулями. Адрес контракта одновременно выполняет функции банковского счета и контактных данных.
|
||||
|
||||
### Архитектура
|
||||
```
|
||||
@@ -23,7 +79,7 @@ DLE.sol (Один контракт)
|
||||
├── ERC-20 токены (голосующая сила)
|
||||
├── Настраиваемый кворум (% от общего количества токенов)
|
||||
├── Система голосования (проверка баланса токенов)
|
||||
├── Мультиподпись (через токен-холдеров)
|
||||
├── Голосование токен‑холдеров
|
||||
├── Модули (добавляемые через голосование)
|
||||
├── Мультичейн синхронизация
|
||||
└── Полное управление данными DLE через кворум
|
||||
@@ -70,13 +126,12 @@ DLE.sol (Один контракт)
|
||||
- **Изменение процента кворума** через кворум
|
||||
- **Изменение текущей цепочки** через кворум
|
||||
|
||||
#### 5. Мультиподпись через токен-холдеров
|
||||
- **Описание**: Система подписей для критических операций
|
||||
#### 5. Голосование токен‑холдеров
|
||||
- **Описание**: Критические операции подтверждаются голосованием держателей токенов
|
||||
- **Функции**:
|
||||
- Подписание операций токен-холдерами
|
||||
- Проверка баланса токенов при подписи
|
||||
- Сбор подписей до достижения кворума
|
||||
- Выполнение операций после сбора подписей
|
||||
- Подача голосов за/против с учетом голосующей силы
|
||||
- Подсчет голосов по снапшотам `ERC20Votes`
|
||||
- Исполнение операций после достижения кворума
|
||||
|
||||
#### 6. Казначейские функции
|
||||
- **Описание**: Управление финансами DLE через голосование
|
||||
@@ -110,7 +165,7 @@ DLE может владеть токенами других DLE и участв
|
||||
#### Механизм работы
|
||||
1. **DLE A** владеет токенами **DLE B**
|
||||
2. **Голос DLE A** в **DLE B** прямо пропорционален количеству токенов **DLE B** на балансе **DLE A**
|
||||
3. Для участия в голосовании **DLE B** холдеры **DLE A** должны собрать **кворум мультиподписей** внутри **DLE A**
|
||||
3. Для участия в голосовании **DLE B** холдеры **DLE A** должны собрать **кворум голосов** внутри **DLE A**
|
||||
4. После достижения кворума подписей **DLE A** может голосовать в **DLE B** как единое целое
|
||||
|
||||
### Новые возможности изменения данных DLE ✅
|
||||
@@ -165,7 +220,7 @@ event CurrentChainIdUpdated(uint256 oldChainId, uint256 newChainId);
|
||||
|
||||
#### 4. Синхронизация
|
||||
- Изменения синхронизируются во все поддерживаемые цепочки
|
||||
- Merkle proofs обеспечивают безопасность cross-chain операций
|
||||
- EIP‑712 подписи холдеров обеспечивают безопасность cross-chain исполнения (без мостов)
|
||||
|
||||
### Примеры использования
|
||||
|
||||
@@ -203,7 +258,7 @@ event CurrentChainIdUpdated(uint256 oldChainId, uint256 newChainId);
|
||||
#### Защита от злоупотреблений
|
||||
- Все изменения только через кворум
|
||||
- Проверка баланса токенов при голосовании
|
||||
- Merkle proofs для cross-chain безопасности
|
||||
- EIP‑712 подписи и проверка снапшотов для cross-chain безопасности
|
||||
|
||||
#### Аудит изменений
|
||||
- Все изменения логируются в событиях
|
||||
@@ -218,7 +273,7 @@ DLE может владеть токенами других DLE и участв
|
||||
#### Механизм работы
|
||||
1. **DLE A** владеет токенами **DLE B**
|
||||
2. **Голос DLE A** в **DLE B** прямо пропорционален количеству токенов **DLE B** на балансе **DLE A**
|
||||
3. Для участия в голосовании **DLE B** холдеры **DLE A** должны собрать **кворум мультиподписей** внутри **DLE A**
|
||||
3. Для участия в голосовании **DLE B** холдеры **DLE A** должны собрать **кворум голосов** внутри **DLE A**
|
||||
4. После достижения кворума подписей **DLE A** может голосовать в **DLE B** как единое целое
|
||||
|
||||
#### Пример
|
||||
@@ -228,8 +283,8 @@ DLE может владеть токенами других DLE и участв
|
||||
- **DLE B** получает от **DLE A** подпись на **10% голосов**
|
||||
|
||||
#### Технические требования
|
||||
- Система сбора мультиподписей внутри DLE для внешнего голосования
|
||||
- Проверка кворума подписей перед активацией голоса DLE
|
||||
- Система сбора голосов внутри DLE для внешнего голосования
|
||||
- Проверка прав через голосование
|
||||
- Прямо пропорциональный подсчет голосов по количеству токенов
|
||||
- Интерфейсы для взаимодействия между DLE
|
||||
|
||||
@@ -272,11 +327,11 @@ function DLEBManagementInterface({ dleBAddress }) {
|
||||
- URL: `http://localhost:5173/dle-management`
|
||||
- Встраивание компонентов управления DLE B
|
||||
- Безопасное подписание транзакций для DLE B
|
||||
- Проверка прав через мультиподпись
|
||||
- Проверка прав через голосование
|
||||
|
||||
### Технические требования
|
||||
- Один адрес = универсальная точка входа
|
||||
- Безопасность мультиподписи через токен-холдеров
|
||||
- Безопасность коллективного голосования токен‑холдеров по снапшотам
|
||||
- Масштабируемость через модули
|
||||
- Поддержка аудио/видео коммуникации
|
||||
- Совместимость с существующими стандартами (ERC-20, ERC-721)
|
||||
@@ -299,12 +354,12 @@ DLE должен функционировать в нескольких блок
|
||||
#### 2. Синхронные токены управления
|
||||
- Одинаковое количество токенов для каждого партнера во всех сетях
|
||||
- Синхронизация операций с токенами между всеми развернутыми сетями
|
||||
- Все операции с токенами только через мультиподпись и кворум
|
||||
- Все операции с токенами только через кворум голосов
|
||||
- Защита от double-spending и рассинхронизации
|
||||
|
||||
#### 3. Single-Chain Governance система
|
||||
- Инициатор предложения выбирает ОДНУ сеть для голосования
|
||||
- Все токен-холдеры участвуют в мультиподписи только в выбранной сети
|
||||
- Все токен-холдеры участвуют в голосовании только в выбранной сети
|
||||
- Инициатор устанавливает таймлок для предложения
|
||||
- Проверка балансов токен-холдеров при подписании
|
||||
- Исполнение решения происходит во всех целевых сетях
|
||||
@@ -408,7 +463,7 @@ contract DLE_SingleChainGovernance {
|
||||
ERC-4337 предоставляет стандартную инфраструктуру для смарт-контракт кошельков с универсальностью (один адрес во всех цепочках) и готовыми решениями для оптимизации газа.
|
||||
|
||||
#### Компоненты ERC-4337
|
||||
- **Smart Contract Wallets** - встроенная мультиподпись
|
||||
- **Smart Contract Wallets** — инфраструктура аккаунтов (опционально для UX)
|
||||
- **Bundlers** - оптимизация газа через агрегацию транзакций
|
||||
- **Paymasters** - гибкая оплата транзакций
|
||||
- **Account Abstraction** - универсальность и стандартизация
|
||||
@@ -449,7 +504,7 @@ ERC-4337 распространяется под лицензией **CC0** (Pub
|
||||
- ✅ **ERC-20** - токены управления
|
||||
- ✅ **Governance** - система голосования
|
||||
- ✅ **Access Control** - роли и разрешения
|
||||
- ✅ **Multisig** - мультиподпись
|
||||
(устарело) Multisig — используем голосование токен‑холдеров (ERC20Votes)
|
||||
- ✅ **Timelock** - задержки выполнения
|
||||
|
||||
### 2. **ERC-4337** (аудит: Trail of Bits)
|
||||
@@ -475,7 +530,7 @@ contract DLE is ERC20, Governor, TimelockController {
|
||||
|
||||
### **Компоненты для интеграции:**
|
||||
- **ERC-20** - токен управления DLE
|
||||
- **Governor** - система голосования с мультиподписью
|
||||
- **Governor** - система голосования
|
||||
- **TimelockController** - настраиваемые таймлоки
|
||||
- **Account Abstraction** - универсальность адреса
|
||||
|
||||
@@ -493,10 +548,13 @@ contract DLE is ERC20, Governor, TimelockController {
|
||||
|
||||
### ✅ **Надежность**
|
||||
- Временем проверенные решения
|
||||
- Простая логика мультиподписи токен-холдеров
|
||||
- Простая логика коллективного голосования токен‑холдеров
|
||||
- Понятные механизмы таймлоков
|
||||
|
||||
### ✅ **Совместимость**
|
||||
- Стандартные интерфейсы Ethereum
|
||||
- Совместимость с существующими кошельками
|
||||
- Легкая интеграция с DeFi протоколами
|
||||
- Легкая интеграция с DeFi протоколами
|
||||
|
||||
### Примечание про ERC-4337 (опционально)
|
||||
- Может использоваться в кошельках/окружении для UX (userOps), но не является частью ядра DLE v2.
|
||||
@@ -84,18 +84,20 @@ export async function createProposal(dleAddress, proposalData) {
|
||||
const signer = await provider.getSigner();
|
||||
|
||||
// ABI для создания предложения
|
||||
const dleAbi = [
|
||||
"function createProposal(string memory _description, uint256 _duration, bytes memory _operation, uint256 _governanceChainId) external returns (uint256)"
|
||||
const dleAbi = [
|
||||
"function createProposal(string memory _description, uint256 _duration, bytes memory _operation, uint256 _governanceChainId, uint256[] memory _targetChains, uint256 _timelockDelay) external returns (uint256)"
|
||||
];
|
||||
|
||||
const dle = new ethers.Contract(dleAddress, dleAbi, signer);
|
||||
|
||||
// Создаем предложение
|
||||
const tx = await dle.createProposal(
|
||||
const tx = await dle.createProposal(
|
||||
proposalData.description,
|
||||
proposalData.duration,
|
||||
proposalData.operation,
|
||||
proposalData.governanceChainId
|
||||
proposalData.governanceChainId,
|
||||
proposalData.targetChains || [],
|
||||
proposalData.timelockDelay || 0
|
||||
);
|
||||
|
||||
// Ждем подтверждения транзакции
|
||||
|
||||
@@ -683,6 +683,61 @@
|
||||
<div class="preview-item">
|
||||
<strong>💰 Общая стоимость:</strong> ~${{ totalDeployCost.toFixed(2) }}
|
||||
</div>
|
||||
|
||||
<!-- Предсказанные адреса (CREATE2) -->
|
||||
<div class="preview-item predicted-addresses">
|
||||
<div class="predicted-header">
|
||||
<strong>📍 Предсказанные адреса DLE:</strong>
|
||||
</div>
|
||||
<ul class="networks-list" v-if="Object.keys(predictedAddresses).length">
|
||||
<li v-for="net in selectedNetworkDetails" :key="net.chainId">
|
||||
{{ net.name }} ({{ net.chainId }}):
|
||||
<code class="addr">{{ predictedAddresses[net.chainId] || '—' }}</code>
|
||||
<button
|
||||
v-if="predictedAddresses[net.chainId]"
|
||||
type="button"
|
||||
class="btn btn-xs btn-outline-secondary"
|
||||
@click="copyToClipboard(predictedAddresses[net.chainId])"
|
||||
>Копировать</button>
|
||||
</li>
|
||||
</ul>
|
||||
<small class="text-muted" v-else>Адреса вычисляются автоматически при выборе сетей.</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Ключи блокчейн-сканов (опционально) -->
|
||||
<div v-if="selectedNetworks.length > 0" class="preview-section explorer-keys-section">
|
||||
<h4>🧩 Ключи блокчейн-сканов (опционально для авто-верификации)</h4>
|
||||
<div class="explorer-keys-grid">
|
||||
<div
|
||||
v-for="network in selectedNetworkDetails"
|
||||
:key="network.chainId"
|
||||
class="explorer-key-item"
|
||||
>
|
||||
<label class="explorer-key-label">
|
||||
{{ network.name }} (Chain ID: {{ network.chainId }})
|
||||
</label>
|
||||
<div class="explorer-key-input">
|
||||
<input
|
||||
:type="explorerKeyVisibility[network.chainId] ? 'text' : 'password'"
|
||||
class="form-control"
|
||||
:placeholder="`API ключ скана для ${network.name}`"
|
||||
v-model="explorerApiKeys[network.chainId]"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<button type="button" class="btn btn-secondary btn-sm"
|
||||
@click="explorerKeyVisibility[network.chainId] = !explorerKeyVisibility[network.chainId]">
|
||||
{{ explorerKeyVisibility[network.chainId] ? 'Скрыть' : 'Показать' }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-danger btn-sm" @click="explorerApiKeys[network.chainId] = ''">
|
||||
Очистить
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="explorer-keys-actions">
|
||||
<label><input type="checkbox" v-model="persistExplorerKeys" /> Сохранить локально до конца деплоя</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Приватный ключ -->
|
||||
@@ -903,6 +958,13 @@ const availableNetworks = ref([]);
|
||||
const isLoadingNetworks = ref(false);
|
||||
const totalDeployCost = ref(0);
|
||||
const predictedAddress = ref('');
|
||||
const predictedAddresses = reactive({}); // { chainId: address }
|
||||
const isPredicting = ref(false);
|
||||
|
||||
// Ключи блокчейн-сканов (локально)
|
||||
const explorerApiKeys = reactive({}); // { [chainId]: apiKey }
|
||||
const explorerKeyVisibility = reactive({});
|
||||
const persistExplorerKeys = ref(false);
|
||||
|
||||
// Состояние для приватных ключей
|
||||
const useSameKeyForAllChains = ref(true);
|
||||
@@ -958,6 +1020,61 @@ const hasSelectedNetworks = computed(() => {
|
||||
return selectedNetworks.value.length > 0;
|
||||
});
|
||||
|
||||
// Инициализация полей ключей при смене выбранных сетей
|
||||
watch(selectedNetworkDetails, (nets) => {
|
||||
nets.forEach(n => {
|
||||
if (!(n.chainId in explorerKeyVisibility)) explorerKeyVisibility[n.chainId] = false;
|
||||
if (persistExplorerKeys.value) {
|
||||
const saved = localStorage.getItem(`scan_key_${n.chainId}`);
|
||||
if (saved && !explorerApiKeys[n.chainId]) explorerApiKeys[n.chainId] = saved;
|
||||
}
|
||||
});
|
||||
if (nets && nets.length > 0) predictAddresses();
|
||||
}, { immediate: true });
|
||||
|
||||
watch(persistExplorerKeys, (val) => {
|
||||
if (!val) return;
|
||||
Object.entries(explorerApiKeys).forEach(([chainId, key]) => {
|
||||
if (key) localStorage.setItem(`scan_key_${chainId}`, key);
|
||||
});
|
||||
});
|
||||
|
||||
function clearExplorerKeys() {
|
||||
Object.keys(explorerApiKeys).forEach((k) => explorerApiKeys[k] = '');
|
||||
Object.keys(localStorage)
|
||||
.filter(k => k.startsWith('scan_key_'))
|
||||
.forEach(k => localStorage.removeItem(k));
|
||||
}
|
||||
|
||||
// Предсказание адресов (упрощенно через бэкенд)
|
||||
async function predictAddresses() {
|
||||
try {
|
||||
isPredicting.value = true;
|
||||
const payload = {
|
||||
name: dleSettings.name,
|
||||
symbol: dleSettings.tokenSymbol,
|
||||
selectedNetworks: selectedNetworkDetails.value.map(n => n.chainId)
|
||||
};
|
||||
const resp = await axios.post('/dle-v2/predict-addresses', payload);
|
||||
if (resp.data && resp.data.success && resp.data.data) {
|
||||
// ожидаем вид { [chainId]: address }
|
||||
Object.keys(predictedAddresses).forEach(k => delete predictedAddresses[k]);
|
||||
Object.assign(predictedAddresses, resp.data.data);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Ошибка расчета предсказанных адресов:', e);
|
||||
alert('Не удалось рассчитать предсказанные адреса');
|
||||
} finally {
|
||||
isPredicting.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function copyToClipboard(text) {
|
||||
navigator.clipboard?.writeText(text).then(() => {
|
||||
// no-op
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
// Информация о выбранном стандарте токена
|
||||
const selectedTokenStandardInfo = computed(() => {
|
||||
return tokenStandardsData[dleSettings.tokenStandard] || null;
|
||||
@@ -2306,7 +2423,8 @@ const deploySmartContracts = async () => {
|
||||
currentChainId: dleSettings.selectedNetworks[0] || 1,
|
||||
|
||||
// Приватный ключ для деплоя
|
||||
privateKey: unifiedPrivateKey.value
|
||||
privateKey: unifiedPrivateKey.value,
|
||||
explorerApiKeys: explorerApiKeys
|
||||
};
|
||||
|
||||
console.log('Данные для деплоя DLE:', deployData);
|
||||
@@ -2333,6 +2451,12 @@ const deploySmartContracts = async () => {
|
||||
// Перенаправляем на главную страницу управления
|
||||
router.push('/management');
|
||||
}, 2000);
|
||||
if (!persistExplorerKeys.value) {
|
||||
Object.keys(explorerApiKeys).forEach((k) => explorerApiKeys[k] = '');
|
||||
Object.keys(localStorage)
|
||||
.filter(k => k.startsWith('scan_key_'))
|
||||
.forEach(k => localStorage.removeItem(k));
|
||||
}
|
||||
|
||||
} else {
|
||||
showDeployProgress.value = false;
|
||||
@@ -2375,6 +2499,15 @@ const validateCoordinates = (coordinates) => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.explorer-keys-section { margin-top: 16px; }
|
||||
.explorer-keys-grid { display: grid; grid-template-columns: 1fr; gap: 12px; }
|
||||
.explorer-key-item { display: flex; flex-direction: column; gap: 8px; }
|
||||
.explorer-key-input { display: flex; gap: 8px; align-items: center; flex-wrap: nowrap; }
|
||||
.explorer-key-input input { flex: 1 1 auto; width: auto; min-width: 0; }
|
||||
.explorer-keys-actions { margin-top: 8px; display: flex; gap: 12px; align-items: center; }
|
||||
@media (min-width: 768px) {
|
||||
.explorer-keys-grid { grid-template-columns: 1fr 1fr; }
|
||||
}
|
||||
.settings-panel {
|
||||
padding: var(--block-padding);
|
||||
background-color: var(--color-light);
|
||||
|
||||
@@ -214,6 +214,15 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Timelock -->
|
||||
<div class="form-section">
|
||||
<h5>⏳ Timelock</h5>
|
||||
<div class="form-group-inline">
|
||||
<label for="timelockHours">Задержка исполнения (часы):</label>
|
||||
<input id="timelockHours" type="number" min="0" step="1" v-model.number="newProposal.timelockHours" class="form-control small" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Выбор цепочки для кворума -->
|
||||
<div class="form-section">
|
||||
<h5>🔗 Выбор цепочки для кворума</h5>
|
||||
@@ -239,7 +248,23 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Тип операции -->
|
||||
|
||||
|
||||
<!-- Целевые сети для исполнения (мультиселект) -->
|
||||
<div class="form-section" v-if="showTargetChains">
|
||||
<h5>🎯 Целевые сети для исполнения</h5>
|
||||
<div class="targets-grid">
|
||||
<label v-for="chain in availableChains" :key="chain.chainId" class="target-item">
|
||||
<input type="checkbox" :value="chain.chainId" v-model="newProposal.targetChains" />
|
||||
<span>{{ chain.name }} ({{ chain.chainId }})</span>
|
||||
</label>
|
||||
</div>
|
||||
<small class="text-muted">Для offchain‑действий целевые сети не требуются.</small>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- Тип операции (последним блоком) -->
|
||||
<div class="form-section">
|
||||
<h5>⚙️ Тип операции</h5>
|
||||
|
||||
@@ -458,7 +483,22 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Предварительный просмотр -->
|
||||
<!-- Действия -->
|
||||
<div class="form-actions">
|
||||
<button
|
||||
class="btn btn-success"
|
||||
@click="createProposal"
|
||||
:disabled="!isFormValid || isCreating"
|
||||
>
|
||||
<i class="fas fa-paper-plane"></i>
|
||||
{{ isCreating ? 'Создание...' : 'Создать предложение' }}
|
||||
</button>
|
||||
<button class="btn btn-secondary" @click="resetForm">
|
||||
<i class="fas fa-undo"></i> Сбросить
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Предварительный просмотр (в конце формы) -->
|
||||
<div class="form-section">
|
||||
<h5>👁️ Предварительный просмотр</h5>
|
||||
<div class="preview-card">
|
||||
@@ -480,21 +520,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Действия -->
|
||||
<div class="form-actions">
|
||||
<button
|
||||
class="btn btn-success"
|
||||
@click="createProposal"
|
||||
:disabled="!isFormValid || isCreating"
|
||||
>
|
||||
<i class="fas fa-paper-plane"></i>
|
||||
{{ isCreating ? 'Создание...' : 'Создать предложение' }}
|
||||
</button>
|
||||
<button class="btn btn-secondary" @click="resetForm">
|
||||
<i class="fas fa-undo"></i> Сбросить
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div> <!-- Закрываем div для авторизованных пользователей -->
|
||||
</div>
|
||||
@@ -507,6 +532,11 @@ import { useRouter, useRoute } from 'vue-router';
|
||||
import { useAuthContext } from '@/composables/useAuth';
|
||||
import BaseLayout from '../../components/BaseLayout.vue';
|
||||
import { getDLEInfo, loadProposals, createProposal as createProposalAPI, voteForProposal as voteForProposalAPI, executeProposal as executeProposalAPI, getSupportedChains } from '../../utils/dle-contract.js';
|
||||
const showTargetChains = computed(() => {
|
||||
// Для offchain-действий не требуется ончейн исполнение (здесь типы пока ончейн)
|
||||
// Можно расширить логику при появлении offchain типа
|
||||
return true;
|
||||
});
|
||||
import wsClient from '../../utils/websocket.js';
|
||||
import { ethers } from 'ethers';
|
||||
|
||||
@@ -552,6 +582,8 @@ const newProposal = ref({
|
||||
description: '',
|
||||
duration: 7,
|
||||
governanceChainId: null,
|
||||
timelockHours: 0,
|
||||
targetChains: [],
|
||||
operationType: '',
|
||||
operationParams: {
|
||||
to: '',
|
||||
@@ -584,6 +616,7 @@ const isFormValid = computed(() => {
|
||||
newProposal.value.duration > 0 &&
|
||||
newProposal.value.governanceChainId &&
|
||||
newProposal.value.operationType &&
|
||||
newProposal.value.timelockHours >= 0 &&
|
||||
validateOperationParams()
|
||||
);
|
||||
});
|
||||
@@ -981,7 +1014,9 @@ async function createProposal() {
|
||||
description: newProposal.value.description,
|
||||
duration: newProposal.value.duration * 24 * 60 * 60, // конвертируем в секунды
|
||||
operation: operation,
|
||||
governanceChainId: newProposal.value.governanceChainId
|
||||
governanceChainId: newProposal.value.governanceChainId,
|
||||
targetChains: showTargetChains.value ? newProposal.value.targetChains : [],
|
||||
timelockDelay: (newProposal.value.timelockHours || 0) * 3600
|
||||
});
|
||||
|
||||
console.log('Предложение создано:', result);
|
||||
|
||||
Reference in New Issue
Block a user