ваше сообщение коммита
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",
|
"_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",
|
"_format": "hh-sol-cache-2",
|
||||||
"files": {
|
"files": {
|
||||||
"/home/alex/Digital_Legal_Entity(DLE)/backend/contracts/DLE.sol": {
|
"/home/alex/Digital_Legal_Entity(DLE)/backend/contracts/DLE.sol": {
|
||||||
"lastModificationDate": 1754485037554,
|
"lastModificationDate": 1754682456067,
|
||||||
"contentHash": "f121cb518877db715ab5cd2e3ee5ff3a",
|
"contentHash": "6d46f24614a8d6c838144dcfad200e26",
|
||||||
"sourceName": "contracts/DLE.sol",
|
"sourceName": "contracts/DLE.sol",
|
||||||
"solcConfig": {
|
"solcConfig": {
|
||||||
"version": "0.8.20",
|
"version": "0.8.20",
|
||||||
@@ -32,8 +32,10 @@
|
|||||||
},
|
},
|
||||||
"imports": [
|
"imports": [
|
||||||
"@openzeppelin/contracts/token/ERC20/ERC20.sol",
|
"@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/ReentrancyGuard.sol",
|
||||||
"@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"
|
"@openzeppelin/contracts/utils/cryptography/ECDSA.sol"
|
||||||
],
|
],
|
||||||
"versionPragmas": [
|
"versionPragmas": [
|
||||||
"^0.8.20"
|
"^0.8.20"
|
||||||
@@ -348,6 +350,864 @@
|
|||||||
"artifacts": [
|
"artifacts": [
|
||||||
"Hashes"
|
"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;
|
pragma solidity ^0.8.20;
|
||||||
|
|
||||||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
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/ReentrancyGuard.sol";
|
||||||
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
|
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @title DLE (Digital Legal Entity)
|
* @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 {
|
struct DLEInfo {
|
||||||
string name;
|
string name;
|
||||||
string symbol;
|
string symbol;
|
||||||
@@ -53,11 +57,15 @@ contract DLE is ERC20, ReentrancyGuard {
|
|||||||
uint256 forVotes;
|
uint256 forVotes;
|
||||||
uint256 againstVotes;
|
uint256 againstVotes;
|
||||||
bool executed;
|
bool executed;
|
||||||
uint256 deadline;
|
bool canceled;
|
||||||
|
uint256 deadline; // конец периода голосования (sec)
|
||||||
address initiator;
|
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(address => bool) hasVoted;
|
||||||
mapping(uint256 => bool) chainVoteSynced; // Синхронизация голосов между цепочками
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -74,15 +82,11 @@ contract DLE is ERC20, ReentrancyGuard {
|
|||||||
|
|
||||||
// Предложения
|
// Предложения
|
||||||
mapping(uint256 => Proposal) public proposals;
|
mapping(uint256 => Proposal) public proposals;
|
||||||
|
uint256[] public allProposalIds;
|
||||||
|
|
||||||
// Мульти-чейн
|
// Мульти-чейн
|
||||||
mapping(uint256 => bool) public supportedChains;
|
mapping(uint256 => bool) public supportedChains;
|
||||||
uint256[] public supportedChainIds;
|
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(
|
event DLEInitialized(
|
||||||
@@ -101,21 +105,29 @@ contract DLE is ERC20, ReentrancyGuard {
|
|||||||
event ProposalCreated(uint256 proposalId, address initiator, string description);
|
event ProposalCreated(uint256 proposalId, address initiator, string description);
|
||||||
event ProposalVoted(uint256 proposalId, address voter, bool support, uint256 votingPower);
|
event ProposalVoted(uint256 proposalId, address voter, bool support, uint256 votingPower);
|
||||||
event ProposalExecuted(uint256 proposalId, bytes operation);
|
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 ModuleAdded(bytes32 moduleId, address moduleAddress);
|
||||||
event ModuleRemoved(bytes32 moduleId);
|
event ModuleRemoved(bytes32 moduleId);
|
||||||
event CrossChainExecutionSync(uint256 proposalId, uint256 fromChainId, uint256 toChainId);
|
event ProposalExecutionApprovedInChain(uint256 proposalId, uint256 chainId);
|
||||||
event CrossChainVoteSync(uint256 proposalId, uint256 fromChainId, uint256 toChainId);
|
|
||||||
event ChainAdded(uint256 chainId);
|
event ChainAdded(uint256 chainId);
|
||||||
event ChainRemoved(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 DLEInfoUpdated(string name, string symbol, string location, string coordinates, uint256 jurisdiction, uint256 oktmo, string[] okvedCodes, uint256 kpp);
|
||||||
event QuorumPercentageUpdated(uint256 oldQuorumPercentage, uint256 newQuorumPercentage);
|
event QuorumPercentageUpdated(uint256 oldQuorumPercentage, uint256 newQuorumPercentage);
|
||||||
event CurrentChainIdUpdated(uint256 oldChainId, uint256 newChainId);
|
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(
|
constructor(
|
||||||
DLEConfig memory config,
|
DLEConfig memory config,
|
||||||
uint256 _currentChainId
|
uint256 _currentChainId
|
||||||
) ERC20(config.name, config.symbol) {
|
) ERC20(config.name, config.symbol) ERC20Permit(config.name) {
|
||||||
dleInfo = DLEInfo({
|
dleInfo = DLEInfo({
|
||||||
name: config.name,
|
name: config.name,
|
||||||
symbol: config.symbol,
|
symbol: config.symbol,
|
||||||
@@ -143,9 +155,13 @@ contract DLE is ERC20, ReentrancyGuard {
|
|||||||
require(config.initialPartners.length > 0, "No initial partners");
|
require(config.initialPartners.length > 0, "No initial partners");
|
||||||
|
|
||||||
for (uint256 i = 0; i < config.initialPartners.length; i++) {
|
for (uint256 i = 0; i < config.initialPartners.length; i++) {
|
||||||
require(config.initialPartners[i] != address(0), "Zero address");
|
address partner = config.initialPartners[i];
|
||||||
require(config.initialAmounts[i] > 0, "Zero amount");
|
uint256 amount = config.initialAmounts[i];
|
||||||
_mint(config.initialPartners[i], 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);
|
emit InitialTokensDistributed(config.initialPartners, config.initialAmounts);
|
||||||
@@ -174,12 +190,14 @@ contract DLE is ERC20, ReentrancyGuard {
|
|||||||
string memory _description,
|
string memory _description,
|
||||||
uint256 _duration,
|
uint256 _duration,
|
||||||
bytes memory _operation,
|
bytes memory _operation,
|
||||||
uint256 _governanceChainId
|
uint256 _governanceChainId,
|
||||||
|
uint256[] memory _targetChains,
|
||||||
|
uint256 _timelockDelay
|
||||||
) external returns (uint256) {
|
) external returns (uint256) {
|
||||||
require(balanceOf(msg.sender) > 0, "Must hold tokens to create proposal");
|
require(balanceOf(msg.sender) > 0, "Must hold tokens to create proposal");
|
||||||
require(_duration > 0, "Duration must be positive");
|
require(_duration > 0, "Duration must be positive");
|
||||||
require(supportedChains[_governanceChainId], "Chain not supported");
|
require(supportedChains[_governanceChainId], "Chain not supported");
|
||||||
require(checkChainConnection(_governanceChainId), "Chain not available");
|
require(_timelockDelay <= 365 days, "Timelock too big");
|
||||||
|
|
||||||
uint256 proposalId = proposalCounter++;
|
uint256 proposalId = proposalCounter++;
|
||||||
Proposal storage proposal = proposals[proposalId];
|
Proposal storage proposal = proposals[proposalId];
|
||||||
@@ -192,8 +210,22 @@ contract DLE is ERC20, ReentrancyGuard {
|
|||||||
proposal.deadline = block.timestamp + _duration;
|
proposal.deadline = block.timestamp + _duration;
|
||||||
proposal.initiator = msg.sender;
|
proposal.initiator = msg.sender;
|
||||||
proposal.operation = _operation;
|
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 ProposalCreated(proposalId, msg.sender, _description);
|
||||||
|
emit ProposalGovernanceChainSet(proposalId, _governanceChainId);
|
||||||
|
emit ProposalTargetsSet(proposalId, _targetChains);
|
||||||
|
emit ProposalTimelockSet(proposalId, proposal.timelock);
|
||||||
return proposalId;
|
return proposalId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,9 +240,10 @@ contract DLE is ERC20, ReentrancyGuard {
|
|||||||
require(block.timestamp < proposal.deadline, "Voting ended");
|
require(block.timestamp < proposal.deadline, "Voting ended");
|
||||||
require(!proposal.executed, "Proposal already executed");
|
require(!proposal.executed, "Proposal already executed");
|
||||||
require(!proposal.hasVoted[msg.sender], "Already voted");
|
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;
|
proposal.hasVoted[msg.sender] = true;
|
||||||
|
|
||||||
if (_support) {
|
if (_support) {
|
||||||
@@ -222,53 +255,7 @@ contract DLE is ERC20, ReentrancyGuard {
|
|||||||
emit ProposalVoted(_proposalId, msg.sender, _support, votingPower);
|
emit ProposalVoted(_proposalId, msg.sender, _support, votingPower);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// УДАЛЕНО: syncVoteFromChain с MerkleProof — небезопасно без доверенного моста
|
||||||
* @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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev Проверить результат предложения
|
* @dev Проверить результат предложения
|
||||||
@@ -281,7 +268,9 @@ contract DLE is ERC20, ReentrancyGuard {
|
|||||||
require(proposal.id == _proposalId, "Proposal does not exist");
|
require(proposal.id == _proposalId, "Proposal does not exist");
|
||||||
|
|
||||||
uint256 totalVotes = proposal.forVotes + proposal.againstVotes;
|
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;
|
quorumReached = totalVotes >= quorumRequired;
|
||||||
passed = quorumReached && proposal.forVotes > proposal.againstVotes;
|
passed = quorumReached && proposal.forVotes > proposal.againstVotes;
|
||||||
@@ -297,6 +286,7 @@ contract DLE is ERC20, ReentrancyGuard {
|
|||||||
Proposal storage proposal = proposals[_proposalId];
|
Proposal storage proposal = proposals[_proposalId];
|
||||||
require(proposal.id == _proposalId, "Proposal does not exist");
|
require(proposal.id == _proposalId, "Proposal does not exist");
|
||||||
require(!proposal.executed, "Proposal already executed");
|
require(!proposal.executed, "Proposal already executed");
|
||||||
|
require(currentChainId == proposal.governanceChainId, "Execute only in governance chain");
|
||||||
|
|
||||||
(bool passed, bool quorumReached) = checkProposalResult(_proposalId);
|
(bool passed, bool quorumReached) = checkProposalResult(_proposalId);
|
||||||
|
|
||||||
@@ -307,6 +297,7 @@ contract DLE is ERC20, ReentrancyGuard {
|
|||||||
"Voting not ended and quorum not reached"
|
"Voting not ended and quorum not reached"
|
||||||
);
|
);
|
||||||
require(passed && quorumReached, "Proposal not passed");
|
require(passed && quorumReached, "Proposal not passed");
|
||||||
|
require(block.timestamp >= proposal.timelock, "Timelock not expired");
|
||||||
|
|
||||||
proposal.executed = true;
|
proposal.executed = true;
|
||||||
|
|
||||||
@@ -317,49 +308,74 @@ contract DLE is ERC20, ReentrancyGuard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev Синхронизировать исполнение из другой цепочки
|
* @dev Отмена предложения до истечения голосования инициатором при наличии достаточной голосующей силы.
|
||||||
* @param _proposalId ID предложения
|
* Это soft-cancel для защиты от явных ошибок. Порог: >= 10% от снапшотного supply.
|
||||||
* @param _fromChainId ID цепочки откуда синхронизируем
|
|
||||||
*/
|
*/
|
||||||
function syncExecutionFromChain(
|
function cancelProposal(uint256 _proposalId, string calldata reason) external {
|
||||||
uint256 _proposalId,
|
|
||||||
uint256 _fromChainId,
|
|
||||||
bytes memory _proof
|
|
||||||
) external {
|
|
||||||
require(supportedChains[_fromChainId], "Chain not supported");
|
|
||||||
require(!executedProposals[_proposalId], "Already executed");
|
|
||||||
|
|
||||||
// Проверяем доказательство исполнения из другой цепочки
|
|
||||||
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 storage proposal = proposals[_proposalId];
|
Proposal storage proposal = proposals[_proposalId];
|
||||||
require(proposal.id == _proposalId, "Proposal does not exist");
|
require(proposal.id == _proposalId, "Proposal does not exist");
|
||||||
require(!proposal.executed, "Proposal already executed");
|
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");
|
||||||
|
|
||||||
executedProposals[_proposalId] = true;
|
proposal.canceled = true;
|
||||||
|
emit ProposalCancelled(_proposalId, reason);
|
||||||
// Исполняем операцию из предложения
|
|
||||||
if (proposal.id == _proposalId) {
|
|
||||||
_executeOperation(proposal.operation);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
emit CrossChainExecutionSync(_proposalId, _fromChainId, currentChainId);
|
// УДАЛЕНО: 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 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");
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 Доступна ли цепочка
|
* @return isAvailable Доступна ли цепочка
|
||||||
*/
|
*/
|
||||||
function checkChainConnection(uint256 _chainId) public view returns (bool isAvailable) {
|
function checkChainConnection(uint256 _chainId) public view returns (bool isAvailable) {
|
||||||
// Проверяем, поддерживается ли цепочка
|
// Упрощенная проверка: цепочка объявлена как поддерживаемая
|
||||||
if (!supportedChains[_chainId]) {
|
return supportedChains[_chainId];
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Проверяем, что Merkle root установлен для цепочки
|
|
||||||
// Это означает, что цепочка активна и готова к синхронизации
|
|
||||||
bytes32 merkleRoot = chainMerkleRoots[_chainId];
|
|
||||||
if (merkleRoot == bytes32(0)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -410,12 +415,8 @@ contract DLE is ERC20, ReentrancyGuard {
|
|||||||
function syncToAllChains(uint256 _proposalId) external {
|
function syncToAllChains(uint256 _proposalId) external {
|
||||||
require(checkSyncReadiness(_proposalId), "Not all chains ready");
|
require(checkSyncReadiness(_proposalId), "Not all chains ready");
|
||||||
|
|
||||||
// Выполняем синхронизацию во все цепочки
|
// В этой версии без внешнего моста синхронизация выполняется
|
||||||
for (uint256 i = 0; i < getSupportedChainCount(); i++) {
|
// через executeProposalBySignatures в целевых сетях.
|
||||||
uint256 chainId = getSupportedChainId(i);
|
|
||||||
syncToChain(_proposalId, chainId);
|
|
||||||
}
|
|
||||||
|
|
||||||
emit SyncCompleted(_proposalId);
|
emit SyncCompleted(_proposalId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -424,26 +425,7 @@ contract DLE is ERC20, ReentrancyGuard {
|
|||||||
* @param _proposalId ID предложения
|
* @param _proposalId ID предложения
|
||||||
* @param _chainId ID цепочки
|
* @param _chainId ID цепочки
|
||||||
*/
|
*/
|
||||||
function syncToChain(uint256 _proposalId, uint256 _chainId) internal {
|
// УДАЛЕНО: syncToChain — не используется в подпись‑ориентированной схеме
|
||||||
// Проверяем, что цепочка поддерживается
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev Получить количество поддерживаемых цепочек
|
* @dev Получить количество поддерживаемых цепочек
|
||||||
@@ -465,14 +447,12 @@ contract DLE is ERC20, ReentrancyGuard {
|
|||||||
* @dev Добавить поддерживаемую цепочку (только для владельцев токенов)
|
* @dev Добавить поддерживаемую цепочку (только для владельцев токенов)
|
||||||
* @param _chainId ID цепочки
|
* @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(!supportedChains[_chainId], "Chain already supported");
|
||||||
require(_chainId != currentChainId, "Cannot add current chain");
|
require(_chainId != currentChainId, "Cannot add current chain");
|
||||||
|
|
||||||
supportedChains[_chainId] = true;
|
supportedChains[_chainId] = true;
|
||||||
supportedChainIds.push(_chainId);
|
supportedChainIds.push(_chainId);
|
||||||
|
|
||||||
emit ChainAdded(_chainId);
|
emit ChainAdded(_chainId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -480,13 +460,10 @@ contract DLE is ERC20, ReentrancyGuard {
|
|||||||
* @dev Удалить поддерживаемую цепочку (только для владельцев токенов)
|
* @dev Удалить поддерживаемую цепочку (только для владельцев токенов)
|
||||||
* @param _chainId ID цепочки
|
* @param _chainId ID цепочки
|
||||||
*/
|
*/
|
||||||
function removeSupportedChain(uint256 _chainId) external {
|
function _removeSupportedChain(uint256 _chainId) internal {
|
||||||
require(balanceOf(msg.sender) > 0, "Must hold tokens to remove chain");
|
|
||||||
require(supportedChains[_chainId], "Chain not supported");
|
require(supportedChains[_chainId], "Chain not supported");
|
||||||
require(_chainId != currentChainId, "Cannot remove current chain");
|
require(_chainId != currentChainId, "Cannot remove current chain");
|
||||||
|
|
||||||
supportedChains[_chainId] = false;
|
supportedChains[_chainId] = false;
|
||||||
|
|
||||||
// Удаляем из массива
|
// Удаляем из массива
|
||||||
for (uint256 i = 0; i < supportedChainIds.length; i++) {
|
for (uint256 i = 0; i < supportedChainIds.length; i++) {
|
||||||
if (supportedChainIds[i] == _chainId) {
|
if (supportedChainIds[i] == _chainId) {
|
||||||
@@ -495,10 +472,6 @@ contract DLE is ERC20, ReentrancyGuard {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Очищаем Merkle root для цепочки
|
|
||||||
delete chainMerkleRoots[_chainId];
|
|
||||||
|
|
||||||
emit ChainRemoved(_chainId);
|
emit ChainRemoved(_chainId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -507,22 +480,13 @@ contract DLE is ERC20, ReentrancyGuard {
|
|||||||
* @param _chainId ID цепочки
|
* @param _chainId ID цепочки
|
||||||
* @param _merkleRoot Merkle root для цепочки
|
* @param _merkleRoot Merkle root для цепочки
|
||||||
*/
|
*/
|
||||||
function setChainMerkleRoot(uint256 _chainId, bytes32 _merkleRoot) external {
|
// УДАЛЕНО: setChainMerkleRoot — небезопасно отдавать любому холдеру
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev Получить Merkle root для цепочки
|
* @dev Получить Merkle root для цепочки
|
||||||
* @param _chainId ID цепочки
|
* @param _chainId ID цепочки
|
||||||
*/
|
*/
|
||||||
function getChainMerkleRoot(uint256 _chainId) external view returns (bytes32) {
|
// УДАЛЕНО: getChainMerkleRoot — устарело
|
||||||
return chainMerkleRoots[_chainId];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev Исполнить операцию
|
* @dev Исполнить операцию
|
||||||
@@ -532,19 +496,7 @@ contract DLE is ERC20, ReentrancyGuard {
|
|||||||
// Декодируем операцию
|
// Декодируем операцию
|
||||||
(bytes4 selector, bytes memory data) = abi.decode(_operation, (bytes4, bytes));
|
(bytes4 selector, bytes memory data) = abi.decode(_operation, (bytes4, bytes));
|
||||||
|
|
||||||
if (selector == bytes4(keccak256("transfer(address,uint256)"))) {
|
if (selector == bytes4(keccak256("updateDLEInfo(string,string,string,string,uint256,uint256,string[],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)"))) {
|
|
||||||
// Операция обновления информации DLE
|
// Операция обновления информации DLE
|
||||||
(string memory name, string memory symbol, string memory location, string memory coordinates,
|
(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));
|
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));
|
(bytes32 moduleId) = abi.decode(data, (bytes32));
|
||||||
_removeModule(moduleId);
|
_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 {
|
} else {
|
||||||
// Неизвестная операция
|
// Неизвестная операция
|
||||||
revert("Unknown operation");
|
revert("Unknown operation");
|
||||||
@@ -654,7 +616,6 @@ contract DLE is ERC20, ReentrancyGuard {
|
|||||||
uint256 _chainId
|
uint256 _chainId
|
||||||
) external returns (uint256) {
|
) external returns (uint256) {
|
||||||
require(supportedChains[_chainId], "Chain not supported");
|
require(supportedChains[_chainId], "Chain not supported");
|
||||||
require(checkChainConnection(_chainId), "Chain not available");
|
|
||||||
require(_moduleAddress != address(0), "Zero address");
|
require(_moduleAddress != address(0), "Zero address");
|
||||||
require(!activeModules[_moduleId], "Module already exists");
|
require(!activeModules[_moduleId], "Module already exists");
|
||||||
require(balanceOf(msg.sender) > 0, "Must hold tokens to create proposal");
|
require(balanceOf(msg.sender) > 0, "Must hold tokens to create proposal");
|
||||||
@@ -693,7 +654,6 @@ contract DLE is ERC20, ReentrancyGuard {
|
|||||||
uint256 _chainId
|
uint256 _chainId
|
||||||
) external returns (uint256) {
|
) external returns (uint256) {
|
||||||
require(supportedChains[_chainId], "Chain not supported");
|
require(supportedChains[_chainId], "Chain not supported");
|
||||||
require(checkChainConnection(_chainId), "Chain not available");
|
|
||||||
require(activeModules[_moduleId], "Module does not exist");
|
require(activeModules[_moduleId], "Module does not exist");
|
||||||
require(balanceOf(msg.sender) > 0, "Must hold tokens to create proposal");
|
require(balanceOf(msg.sender) > 0, "Must hold tokens to create proposal");
|
||||||
|
|
||||||
@@ -782,192 +742,146 @@ contract DLE is ERC20, ReentrancyGuard {
|
|||||||
return currentChainId;
|
return currentChainId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// События для новых функций
|
// ===== Интерфейс аналитики для API =====
|
||||||
event SyncCompleted(uint256 proposalId);
|
function getProposalSummary(uint256 _proposalId) external view returns (
|
||||||
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 (
|
|
||||||
uint256 id,
|
uint256 id,
|
||||||
string memory description,
|
string memory description,
|
||||||
uint256 forVotes,
|
uint256 forVotes,
|
||||||
uint256 againstVotes,
|
uint256 againstVotes,
|
||||||
bool executed,
|
bool executed,
|
||||||
|
bool canceled,
|
||||||
uint256 deadline,
|
uint256 deadline,
|
||||||
address initiator,
|
address initiator,
|
||||||
uint256 chainId
|
uint256 governanceChainId,
|
||||||
|
uint256 timelock,
|
||||||
|
uint256 snapshotTimepoint,
|
||||||
|
uint256[] memory targets
|
||||||
) {
|
) {
|
||||||
DeactivationProposal storage proposal = deactivationProposals[_proposalId];
|
Proposal storage p = proposals[_proposalId];
|
||||||
require(proposal.id == _proposalId, "Deactivation proposal does not exist");
|
require(p.id == _proposalId, "Proposal does not exist");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
proposal.id,
|
p.id,
|
||||||
proposal.description,
|
p.description,
|
||||||
proposal.forVotes,
|
p.forVotes,
|
||||||
proposal.againstVotes,
|
p.againstVotes,
|
||||||
proposal.executed,
|
p.executed,
|
||||||
proposal.deadline,
|
p.canceled,
|
||||||
proposal.initiator,
|
p.deadline,
|
||||||
proposal.chainId
|
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 path = require('path');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const ethers = require('ethers'); // Added ethers for private key validation
|
const ethers = require('ethers'); // Added ethers for private key validation
|
||||||
|
const create2 = require('../utils/create2');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @route POST /api/dle-v2
|
* @route POST /api/dle-v2
|
||||||
@@ -299,3 +300,33 @@ 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 Тарабанов Александр Викторович
|
* Copyright (c) 2024-2025 Тарабанов Александр Викторович
|
||||||
* All rights reserved.
|
* 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);
|
fs.copyFileSync(paramsFile, tempParamsFile);
|
||||||
logger.info(`Файл параметров скопирован успешно`);
|
logger.info(`Файл параметров скопирован успешно`);
|
||||||
|
|
||||||
// Определяем сеть для деплоя (берем первую из выбранных сетей)
|
// Готовим RPC для всех выбранных сетей
|
||||||
const chainId = deployParams.supportedChainIds && deployParams.supportedChainIds.length > 0
|
const rpcUrls = [];
|
||||||
? deployParams.supportedChainIds[0]
|
for (const cid of deployParams.supportedChainIds) {
|
||||||
: 1; // По умолчанию Ethereum
|
logger.info(`Поиск RPC URL для chain_id: ${cid}`);
|
||||||
|
const ru = await getRpcUrlByChainId(cid);
|
||||||
// Получаем rpc_url из базы по chain_id
|
if (!ru) {
|
||||||
logger.info(`Поиск RPC URL для chain_id: ${chainId}`);
|
throw new Error(`RPC URL для сети с chain_id ${cid} не найден в базе данных`);
|
||||||
const rpcUrl = await getRpcUrlByChainId(chainId);
|
}
|
||||||
if (!rpcUrl) {
|
rpcUrls.push(ru);
|
||||||
logger.error(`RPC URL для сети с chain_id ${chainId} не найден в базе данных`);
|
|
||||||
throw new Error(`RPC URL для сети с chain_id ${chainId} не найден в базе данных`);
|
|
||||||
}
|
}
|
||||||
logger.info(`Найден RPC URL для chain_id ${chainId}: ${rpcUrl}`);
|
|
||||||
|
|
||||||
// Проверяем баланс кошелька
|
// Лёгкая проверка баланса в первой сети
|
||||||
|
{
|
||||||
const { ethers } = require('ethers');
|
const { ethers } = require('ethers');
|
||||||
const provider = new ethers.JsonRpcProvider(rpcUrl);
|
const provider = new ethers.JsonRpcProvider(rpcUrls[0]);
|
||||||
const walletAddress = dleParams.privateKey ? new ethers.Wallet(dleParams.privateKey, provider).address : null;
|
const walletAddress = dleParams.privateKey ? new ethers.Wallet(dleParams.privateKey, provider).address : null;
|
||||||
|
|
||||||
if (walletAddress) {
|
if (walletAddress) {
|
||||||
const balance = await provider.getBalance(walletAddress);
|
const balance = await provider.getBalance(walletAddress);
|
||||||
const minBalance = ethers.parseEther("0.00001"); // Временно уменьшено для тестирования
|
const minBalance = ethers.parseEther("0.00001");
|
||||||
logger.info(`Баланс кошелька ${walletAddress}: ${ethers.formatEther(balance)} ETH`);
|
logger.info(`Баланс кошелька ${walletAddress}: ${ethers.formatEther(balance)} ETH`);
|
||||||
|
|
||||||
if (balance < minBalance) {
|
if (balance < minBalance) {
|
||||||
logger.warn(`Недостаточно ETH для деплоя. Баланс: ${ethers.formatEther(balance)} ETH, требуется минимум: ${ethers.formatEther(minBalance)} ETH`);
|
throw new Error(`Недостаточно ETH для деплоя в ${deployParams.supportedChainIds[0]}. Баланс: ${ethers.formatEther(balance)} ETH`);
|
||||||
throw new Error(`Недостаточно ETH для деплоя. Баланс: ${ethers.formatEther(balance)} ETH, требуется минимум: ${ethers.formatEther(minBalance)} ETH. Пополните кошелек через Sepolia faucet: https://sepoliafaucet.com/`);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!dleParams.privateKey) {
|
if (!dleParams.privateKey) {
|
||||||
throw new Error('Приватный ключ для деплоя не передан');
|
throw new Error('Приватный ключ для деплоя не передан');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Маппинг chain_id к именам сетей в Hardhat
|
// Рассчитываем INIT_CODE_HASH автоматически из актуального initCode
|
||||||
const chainIdToNetworkName = {
|
const initCodeHash = await this.computeInitCodeHash(deployParams);
|
||||||
1: 'ethereum',
|
|
||||||
137: 'polygon',
|
|
||||||
56: 'bsc',
|
|
||||||
42161: 'arbitrum',
|
|
||||||
11155111: 'sepolia'
|
|
||||||
};
|
|
||||||
|
|
||||||
const networkName = chainIdToNetworkName[chainId];
|
// Собираем адреса фабрик по сетям (если есть)
|
||||||
if (!networkName) {
|
const factoryAddresses = deployParams.supportedChainIds.map(cid => process.env[`FACTORY_ADDRESS_${cid}`] || '').join(',');
|
||||||
throw new Error(`Сеть с chain_id ${chainId} не поддерживается для деплоя`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Запускаем скрипт деплоя с нужными переменными окружения
|
// Мультисетевой деплой одним вызовом
|
||||||
const result = await this.runDeployScript(paramsFile, {
|
const result = await this.runDeployMultichain(paramsFile, {
|
||||||
rpcUrl,
|
rpcUrls: rpcUrls.join(','),
|
||||||
privateKey: dleParams.privateKey,
|
privateKey: dleParams.privateKey,
|
||||||
networkId: networkName,
|
salt: process.env.CREATE2_SALT,
|
||||||
envNetworkKey: chainId.toString().toUpperCase()
|
initCodeHash,
|
||||||
|
factories: factoryAddresses
|
||||||
});
|
});
|
||||||
|
|
||||||
// Очищаем временные файлы
|
// Очищаем временные файлы
|
||||||
@@ -237,14 +226,12 @@ class DLEV2Service {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Формируем переменные окружения для скрипта деплоя
|
|
||||||
const envVars = {
|
const envVars = {
|
||||||
...process.env,
|
...process.env,
|
||||||
RPC_URL: extraEnv.rpcUrl,
|
RPC_URL: extraEnv.rpcUrl,
|
||||||
PRIVATE_KEY: extraEnv.privateKey
|
PRIVATE_KEY: extraEnv.privateKey
|
||||||
};
|
};
|
||||||
|
|
||||||
// Запускаем скрипт без указания сети, передаем RPC URL и приватный ключ через переменные окружения
|
|
||||||
const hardhatProcess = spawn('npx', ['hardhat', 'run', scriptPath], {
|
const hardhatProcess = spawn('npx', ['hardhat', 'run', scriptPath], {
|
||||||
cwd: path.join(__dirname, '..'),
|
cwd: path.join(__dirname, '..'),
|
||||||
env: envVars,
|
env: envVars,
|
||||||
@@ -266,7 +253,6 @@ class DLEV2Service {
|
|||||||
|
|
||||||
hardhatProcess.on('close', (code) => {
|
hardhatProcess.on('close', (code) => {
|
||||||
try {
|
try {
|
||||||
// Пытаемся извлечь результат из stdout независимо от кода завершения
|
|
||||||
const result = this.extractDeployResult(stdout);
|
const result = this.extractDeployResult(stdout);
|
||||||
resolve(result);
|
resolve(result);
|
||||||
} catch (error) {
|
} 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
|
* Извлекает результат деплоя из stdout
|
||||||
* @param {string} stdout - Вывод скрипта
|
* @param {string} stdout - Вывод скрипта
|
||||||
@@ -358,6 +378,30 @@ class DLEV2Service {
|
|||||||
return [];
|
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();
|
module.exports = new DLEV2Service();
|
||||||
@@ -416,8 +416,9 @@ async function getBot() {
|
|||||||
if (recentMessages && recentMessages.length > 0) {
|
if (recentMessages && recentMessages.length > 0) {
|
||||||
// Преобразуем сообщения в формат для AI
|
// Преобразуем сообщения в формат для AI
|
||||||
history = recentMessages.reverse().map(msg => ({
|
history = recentMessages.reverse().map(msg => ({
|
||||||
role: msg.sender_type === 'user' ? 'user' : 'assistant',
|
// Любые человеческие роли трактуем как 'user', только ответы ассистента — 'assistant'
|
||||||
content: msg.content || '' // content уже расшифрован encryptedDb
|
role: msg.sender_type === 'assistant' ? 'assistant' : 'user',
|
||||||
|
content: msg.content || ''
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
} catch (historyError) {
|
} catch (historyError) {
|
||||||
@@ -465,7 +466,7 @@ async function getBot() {
|
|||||||
sender_type: 'assistant',
|
sender_type: 'assistant',
|
||||||
content: aiResponse,
|
content: aiResponse,
|
||||||
channel: 'telegram',
|
channel: 'telegram',
|
||||||
role: role,
|
role: 'assistant',
|
||||||
direction: 'out',
|
direction: 'out',
|
||||||
created_at: new Date()
|
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
|
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 - Единый Смарт-Контракт с Модульной Архитектурой
|
||||||
|
|
||||||
## 🎯 ПОЛНОЕ ПОНИМАНИЕ ЗАДАЧИ DLE
|
## 🎯 ПОЛНОЕ ПОНИМАНИЕ ЗАДАЧИ DLE
|
||||||
@@ -51,12 +63,12 @@ DLE (Digital Legal Entity) = Универсальная цифровая юри
|
|||||||
|
|
||||||
### **3. СИСТЕМА УПРАВЛЕНИЯ:**
|
### **3. СИСТЕМА УПРАВЛЕНИЯ:**
|
||||||
|
|
||||||
#### **Голосование и мультиподпись:**
|
#### **Голосование токен‑холдеров:**
|
||||||
```
|
```
|
||||||
- Только токен-холдеры участвуют в управлении
|
- Только токен-холдеры участвуют в управлении
|
||||||
- Каждый токен = одна голосующая сила
|
- Каждый токен = одна голосующая сила
|
||||||
- Кворум настраиваемый (например, 60% от общего количества токенов)
|
- Кворум настраиваемый (например, 60% от общего количества токенов)
|
||||||
- Мультиподпись через токен-холдеров (проверка баланса при каждой операции)
|
- Коллективное голосование токен‑холдеров (ERC20Votes снапшоты)
|
||||||
```
|
```
|
||||||
|
|
||||||
#### **Создание предложений:**
|
#### **Создание предложений:**
|
||||||
@@ -101,7 +113,6 @@ DLE (Digital Legal Entity) = Универсальная цифровая юри
|
|||||||
```
|
```
|
||||||
- ERC-20 токены
|
- ERC-20 токены
|
||||||
- Система голосования
|
- Система голосования
|
||||||
- Мультиподпись
|
|
||||||
- Мультичейн синхронизация
|
- Мультичейн синхронизация
|
||||||
- Управление модулями
|
- Управление модулями
|
||||||
- DLEInfo (юридическая информация)
|
- DLEInfo (юридическая информация)
|
||||||
@@ -141,7 +152,7 @@ DLE (Digital Legal Entity) = Универсальная цифровая юри
|
|||||||
```
|
```
|
||||||
- Только токен-холдеры управляют
|
- Только токен-холдеры управляют
|
||||||
- Проверка баланса при каждой операции
|
- Проверка баланса при каждой операции
|
||||||
- Кворум мультиподписей
|
- Кворум голосов - все решения через коллективное голосование
|
||||||
- Синхронизация между цепочками
|
- Синхронизация между цепочками
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -174,7 +185,7 @@ DLE.sol (Основной контракт) + Модули (добавляемы
|
|||||||
1. **Один основной контракт** - управление токенами, голосованием, мультиподписью
|
1. **Один основной контракт** - управление токенами, голосованием, мультиподписью
|
||||||
2. **Модули** - специализированные функции (казначейство, иерархическое голосование, коммуникации)
|
2. **Модули** - специализированные функции (казначейство, иерархическое голосование, коммуникации)
|
||||||
3. **Только токен-холдеры** - никаких админских ролей
|
3. **Только токен-холдеры** - никаких админских ролей
|
||||||
4. **Кворум мультиподписей** - все решения через коллективное голосование
|
4. **Кворум голосов** - все решения через коллективное голосование
|
||||||
5. **Проверка баланса** - при каждой операции
|
5. **Проверка баланса** - при каждой операции
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -190,7 +201,7 @@ DLE.sol
|
|||||||
├── Выбор цепочки для кворума (governanceChainId)
|
├── Выбор цепочки для кворума (governanceChainId)
|
||||||
├── Синхронизация голосов между цепочками
|
├── Синхронизация голосов между цепочками
|
||||||
├── Поддержка множественных цепочек
|
├── Поддержка множественных цепочек
|
||||||
├── Мультиподпись (через токен-холдеров)
|
├── Голосование токен‑холдеров
|
||||||
├── Мультичейн синхронизация
|
├── Мультичейн синхронизация
|
||||||
└── Система модулей (добавление/управление)
|
└── Система модулей (добавление/управление)
|
||||||
```
|
```
|
||||||
@@ -216,7 +227,7 @@ DLE.sol
|
|||||||
- **Выбор цепочки для кворума** - токен-холдер может выбрать любую поддерживаемую цепочку
|
- **Выбор цепочки для кворума** - токен-холдер может выбрать любую поддерживаемую цепочку
|
||||||
- **Синхронизация голосов** - после голосования результаты синхронизируются между цепочками
|
- **Синхронизация голосов** - после голосования результаты синхронизируются между цепочками
|
||||||
- **Поддержка множественных цепочек** - Ethereum, Polygon, BSC и др.
|
- **Поддержка множественных цепочек** - Ethereum, Polygon, BSC и др.
|
||||||
- **Мультиподпись** - через токен-холдеров с проверкой баланса
|
- **Голосование** - через токен‑холдеров с проверкой баланса
|
||||||
- **Мультичейн синхронизация** - одинаковый адрес во всех цепочках
|
- **Мультичейн синхронизация** - одинаковый адрес во всех цепочках
|
||||||
- **Управление модулями** - добавление/удаление через голосование
|
- **Управление модулями** - добавление/удаление через голосование
|
||||||
|
|
||||||
@@ -241,14 +252,14 @@ DLE.sol
|
|||||||
### 5. ExternalDLEModule.sol ✅
|
### 5. ExternalDLEModule.sol ✅
|
||||||
- **Меж-DLE взаимодействие** - управление DLE B через приложение DLE A
|
- **Меж-DLE взаимодействие** - управление DLE B через приложение DLE A
|
||||||
- **Встраивание интерфейсов** - безопасное управление
|
- **Встраивание интерфейсов** - безопасное управление
|
||||||
- **Проверка прав** - через мультиподпись
|
- **Проверка прав** - через голосование токен‑холдеров
|
||||||
- **Аудит действий** - отслеживание операций
|
- **Аудит действий** - отслеживание операций
|
||||||
|
|
||||||
### 6. Мульти-чейн архитектура ✅
|
### 6. Мульти-чейн архитектура ✅
|
||||||
- **CREATE2 деплой** - одинаковый адрес во всех цепочках
|
- **CREATE2 деплой** - одинаковый адрес во всех цепочках
|
||||||
- **Синхронизация состояния** - токены, предложения, голосования
|
- **Синхронизация состояния** - токены, предложения, голосования
|
||||||
- **Создание предложений** - в любой цепочке
|
- **Создание предложений** - в любой цепочке
|
||||||
- **Голосование** - в любой цепочке с синхронизацией
|
- **Голосование** - через токен‑холдеров с проверкой баланса
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -257,7 +268,7 @@ DLE.sol
|
|||||||
### Основные принципы безопасности:
|
### Основные принципы безопасности:
|
||||||
1. **Только токен-холдеры** - никаких админских ролей
|
1. **Только токен-холдеры** - никаких админских ролей
|
||||||
2. **Проверка баланса** - при каждой операции
|
2. **Проверка баланса** - при каждой операции
|
||||||
3. **Кворум мультиподписей** - все решения коллективные
|
3. **Кворум голосов** - все решения коллективные
|
||||||
4. **Простая логика** - минимум уязвимостей
|
4. **Простая логика** - минимум уязвимостей
|
||||||
|
|
||||||
### Защита от атак:
|
### Защита от атак:
|
||||||
@@ -503,13 +514,10 @@ function createProposal(
|
|||||||
uint256 _governanceChainId
|
uint256 _governanceChainId
|
||||||
) external returns (uint256);
|
) external returns (uint256);
|
||||||
|
|
||||||
// Синхронизация голосов между цепочками
|
// Исполнение в целевых сетях по EIP-712 подписям (без мостов)
|
||||||
function syncVoteFromChain(
|
function executeProposalBySignatures(
|
||||||
uint256 _proposalId,
|
uint256 proposalId,
|
||||||
uint256 _fromChainId,
|
bytes[] calldata signatures
|
||||||
uint256 _forVotes,
|
|
||||||
uint256 _againstVotes,
|
|
||||||
bytes memory _proof
|
|
||||||
) external;
|
) external;
|
||||||
|
|
||||||
// Проверка поддерживаемых цепочек
|
// Проверка поддерживаемых цепочек
|
||||||
@@ -517,29 +525,8 @@ function isChainSupported(uint256 _chainId) external view returns (bool);
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Синхронизация между цепочками
|
### Синхронизация между цепочками
|
||||||
```solidity
|
- Результаты голосования фиксируются снапшотами ERC20Votes в governance‑сети.
|
||||||
// Синхронизация токенов
|
- Целевые сети принимают исполнение при верификации EIP‑712 подписей холдеров и кворума на зафиксированном timepoint.
|
||||||
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;
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -940,7 +927,7 @@ contract DLE is ERC20, ReentrancyGuard {
|
|||||||
|
|
||||||
### ✅ Безопасность
|
### ✅ Безопасность
|
||||||
- Никаких админских ролей
|
- Никаких админских ролей
|
||||||
- Простая логика мультиподписи
|
- Простая логика коллективного голосования
|
||||||
- Защита от основных атак
|
- Защита от основных атак
|
||||||
- Прозрачность всех операций
|
- Прозрачность всех операций
|
||||||
|
|
||||||
@@ -962,7 +949,7 @@ contract DLE is ERC20, ReentrancyGuard {
|
|||||||
|
|
||||||
**DLE - это единый смарт-контракт с модульной архитектурой, который:**
|
**DLE - это единый смарт-контракт с модульной архитектурой, который:**
|
||||||
|
|
||||||
1. **Управляется только токен-холдерами** через кворум мультиподписей
|
1. **Управляется только токен‑холдерами** через кворум голосов
|
||||||
2. **Проверяет баланс токенов** при каждой операции
|
2. **Проверяет баланс токенов** при каждой операции
|
||||||
3. **Использует модули** для специализированных функций
|
3. **Использует модули** для специализированных функций
|
||||||
4. **Синхронизируется между цепочками** с одинаковым адресом
|
4. **Синхронизируется между цепочками** с одинаковым адресом
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
### Архитектурные требования
|
### Архитектурные требования
|
||||||
- **Single-Chain Governance**: Голосование происходит только в одной выбранной сети
|
- **Single-Chain Governance**: Голосование происходит только в одной выбранной сети
|
||||||
- **Мультиподпись токен-холдеров**: Все операции требуют кворума подписей
|
- **Кворум голосов токен‑холдеров**: Все операции требуют достижения кворума голосующей силы по снапшотам
|
||||||
- **Настраиваемые таймлоки**: Инициатор устанавливает задержку для каждого предложения
|
- **Настраиваемые таймлоки**: Инициатор устанавливает задержку для каждого предложения
|
||||||
- **Cross-chain исполнение**: Решения выполняются во всех целевых сетях
|
- **Cross-chain исполнение**: Решения выполняются во всех целевых сетях
|
||||||
- **Без админских ролей**: Только коллективное управление через токен-холдеров
|
- **Без админских ролей**: Только коллективное управление через токен-холдеров
|
||||||
@@ -24,11 +24,23 @@
|
|||||||
### Технический стек
|
### Технический стек
|
||||||
- **Frontend**: Vue.js 3 + Composition API
|
- **Frontend**: Vue.js 3 + Composition API
|
||||||
- **Web3**: ethers.js или web3.js
|
- **Web3**: ethers.js или web3.js
|
||||||
- **Контракты**: Solidity + OpenZeppelin + ERC-4337
|
- **Контракты**: Solidity + OpenZeppelin (ERC‑4337 опционально для кошельков/UX)
|
||||||
- **Стили**: Scoped CSS с переменными
|
- **Стили**: 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. БЛОК "ПРЕДЛОЖЕНИЯ" (`/management/proposals`)
|
||||||
|
|
||||||
### Задача 1.1: Создание предложений
|
### Задача 1.1: Создание предложений
|
||||||
|
|||||||
@@ -106,3 +106,18 @@ 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
|
||||||
|
|
||||||
|
### 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 токены (голосующая сила)
|
├── ERC-20 токены (голосующая сила)
|
||||||
├── Настраиваемый кворум (% от общего количества токенов)
|
├── Настраиваемый кворум (% от общего количества токенов)
|
||||||
├── Система голосования (проверка баланса токенов)
|
├── Система голосования (проверка баланса токенов)
|
||||||
├── Мультиподпись (через токен-холдеров)
|
├── Голосование токен‑холдеров
|
||||||
├── Модули (добавляемые через голосование)
|
├── Модули (добавляемые через голосование)
|
||||||
├── Мультичейн синхронизация
|
├── Мультичейн синхронизация
|
||||||
└── Полное управление данными DLE через кворум
|
└── Полное управление данными DLE через кворум
|
||||||
@@ -70,13 +126,12 @@ DLE.sol (Один контракт)
|
|||||||
- **Изменение процента кворума** через кворум
|
- **Изменение процента кворума** через кворум
|
||||||
- **Изменение текущей цепочки** через кворум
|
- **Изменение текущей цепочки** через кворум
|
||||||
|
|
||||||
#### 5. Мультиподпись через токен-холдеров
|
#### 5. Голосование токен‑холдеров
|
||||||
- **Описание**: Система подписей для критических операций
|
- **Описание**: Критические операции подтверждаются голосованием держателей токенов
|
||||||
- **Функции**:
|
- **Функции**:
|
||||||
- Подписание операций токен-холдерами
|
- Подача голосов за/против с учетом голосующей силы
|
||||||
- Проверка баланса токенов при подписи
|
- Подсчет голосов по снапшотам `ERC20Votes`
|
||||||
- Сбор подписей до достижения кворума
|
- Исполнение операций после достижения кворума
|
||||||
- Выполнение операций после сбора подписей
|
|
||||||
|
|
||||||
#### 6. Казначейские функции
|
#### 6. Казначейские функции
|
||||||
- **Описание**: Управление финансами DLE через голосование
|
- **Описание**: Управление финансами DLE через голосование
|
||||||
@@ -110,7 +165,7 @@ DLE может владеть токенами других DLE и участв
|
|||||||
#### Механизм работы
|
#### Механизм работы
|
||||||
1. **DLE A** владеет токенами **DLE B**
|
1. **DLE A** владеет токенами **DLE B**
|
||||||
2. **Голос DLE A** в **DLE B** прямо пропорционален количеству токенов **DLE B** на балансе **DLE A**
|
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** как единое целое
|
4. После достижения кворума подписей **DLE A** может голосовать в **DLE B** как единое целое
|
||||||
|
|
||||||
### Новые возможности изменения данных DLE ✅
|
### Новые возможности изменения данных DLE ✅
|
||||||
@@ -165,7 +220,7 @@ event CurrentChainIdUpdated(uint256 oldChainId, uint256 newChainId);
|
|||||||
|
|
||||||
#### 4. Синхронизация
|
#### 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**
|
1. **DLE A** владеет токенами **DLE B**
|
||||||
2. **Голос DLE A** в **DLE B** прямо пропорционален количеству токенов **DLE B** на балансе **DLE A**
|
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** как единое целое
|
4. После достижения кворума подписей **DLE A** может голосовать в **DLE B** как единое целое
|
||||||
|
|
||||||
#### Пример
|
#### Пример
|
||||||
@@ -228,8 +283,8 @@ DLE может владеть токенами других DLE и участв
|
|||||||
- **DLE B** получает от **DLE A** подпись на **10% голосов**
|
- **DLE B** получает от **DLE A** подпись на **10% голосов**
|
||||||
|
|
||||||
#### Технические требования
|
#### Технические требования
|
||||||
- Система сбора мультиподписей внутри DLE для внешнего голосования
|
- Система сбора голосов внутри DLE для внешнего голосования
|
||||||
- Проверка кворума подписей перед активацией голоса DLE
|
- Проверка прав через голосование
|
||||||
- Прямо пропорциональный подсчет голосов по количеству токенов
|
- Прямо пропорциональный подсчет голосов по количеству токенов
|
||||||
- Интерфейсы для взаимодействия между DLE
|
- Интерфейсы для взаимодействия между DLE
|
||||||
|
|
||||||
@@ -272,11 +327,11 @@ function DLEBManagementInterface({ dleBAddress }) {
|
|||||||
- URL: `http://localhost:5173/dle-management`
|
- URL: `http://localhost:5173/dle-management`
|
||||||
- Встраивание компонентов управления DLE B
|
- Встраивание компонентов управления DLE B
|
||||||
- Безопасное подписание транзакций для DLE B
|
- Безопасное подписание транзакций для DLE B
|
||||||
- Проверка прав через мультиподпись
|
- Проверка прав через голосование
|
||||||
|
|
||||||
### Технические требования
|
### Технические требования
|
||||||
- Один адрес = универсальная точка входа
|
- Один адрес = универсальная точка входа
|
||||||
- Безопасность мультиподписи через токен-холдеров
|
- Безопасность коллективного голосования токен‑холдеров по снапшотам
|
||||||
- Масштабируемость через модули
|
- Масштабируемость через модули
|
||||||
- Поддержка аудио/видео коммуникации
|
- Поддержка аудио/видео коммуникации
|
||||||
- Совместимость с существующими стандартами (ERC-20, ERC-721)
|
- Совместимость с существующими стандартами (ERC-20, ERC-721)
|
||||||
@@ -299,12 +354,12 @@ DLE должен функционировать в нескольких блок
|
|||||||
#### 2. Синхронные токены управления
|
#### 2. Синхронные токены управления
|
||||||
- Одинаковое количество токенов для каждого партнера во всех сетях
|
- Одинаковое количество токенов для каждого партнера во всех сетях
|
||||||
- Синхронизация операций с токенами между всеми развернутыми сетями
|
- Синхронизация операций с токенами между всеми развернутыми сетями
|
||||||
- Все операции с токенами только через мультиподпись и кворум
|
- Все операции с токенами только через кворум голосов
|
||||||
- Защита от double-spending и рассинхронизации
|
- Защита от double-spending и рассинхронизации
|
||||||
|
|
||||||
#### 3. Single-Chain Governance система
|
#### 3. Single-Chain Governance система
|
||||||
- Инициатор предложения выбирает ОДНУ сеть для голосования
|
- Инициатор предложения выбирает ОДНУ сеть для голосования
|
||||||
- Все токен-холдеры участвуют в мультиподписи только в выбранной сети
|
- Все токен-холдеры участвуют в голосовании только в выбранной сети
|
||||||
- Инициатор устанавливает таймлок для предложения
|
- Инициатор устанавливает таймлок для предложения
|
||||||
- Проверка балансов токен-холдеров при подписании
|
- Проверка балансов токен-холдеров при подписании
|
||||||
- Исполнение решения происходит во всех целевых сетях
|
- Исполнение решения происходит во всех целевых сетях
|
||||||
@@ -408,7 +463,7 @@ contract DLE_SingleChainGovernance {
|
|||||||
ERC-4337 предоставляет стандартную инфраструктуру для смарт-контракт кошельков с универсальностью (один адрес во всех цепочках) и готовыми решениями для оптимизации газа.
|
ERC-4337 предоставляет стандартную инфраструктуру для смарт-контракт кошельков с универсальностью (один адрес во всех цепочках) и готовыми решениями для оптимизации газа.
|
||||||
|
|
||||||
#### Компоненты ERC-4337
|
#### Компоненты ERC-4337
|
||||||
- **Smart Contract Wallets** - встроенная мультиподпись
|
- **Smart Contract Wallets** — инфраструктура аккаунтов (опционально для UX)
|
||||||
- **Bundlers** - оптимизация газа через агрегацию транзакций
|
- **Bundlers** - оптимизация газа через агрегацию транзакций
|
||||||
- **Paymasters** - гибкая оплата транзакций
|
- **Paymasters** - гибкая оплата транзакций
|
||||||
- **Account Abstraction** - универсальность и стандартизация
|
- **Account Abstraction** - универсальность и стандартизация
|
||||||
@@ -449,7 +504,7 @@ ERC-4337 распространяется под лицензией **CC0** (Pub
|
|||||||
- ✅ **ERC-20** - токены управления
|
- ✅ **ERC-20** - токены управления
|
||||||
- ✅ **Governance** - система голосования
|
- ✅ **Governance** - система голосования
|
||||||
- ✅ **Access Control** - роли и разрешения
|
- ✅ **Access Control** - роли и разрешения
|
||||||
- ✅ **Multisig** - мультиподпись
|
(устарело) Multisig — используем голосование токен‑холдеров (ERC20Votes)
|
||||||
- ✅ **Timelock** - задержки выполнения
|
- ✅ **Timelock** - задержки выполнения
|
||||||
|
|
||||||
### 2. **ERC-4337** (аудит: Trail of Bits)
|
### 2. **ERC-4337** (аудит: Trail of Bits)
|
||||||
@@ -475,7 +530,7 @@ contract DLE is ERC20, Governor, TimelockController {
|
|||||||
|
|
||||||
### **Компоненты для интеграции:**
|
### **Компоненты для интеграции:**
|
||||||
- **ERC-20** - токен управления DLE
|
- **ERC-20** - токен управления DLE
|
||||||
- **Governor** - система голосования с мультиподписью
|
- **Governor** - система голосования
|
||||||
- **TimelockController** - настраиваемые таймлоки
|
- **TimelockController** - настраиваемые таймлоки
|
||||||
- **Account Abstraction** - универсальность адреса
|
- **Account Abstraction** - универсальность адреса
|
||||||
|
|
||||||
@@ -493,10 +548,13 @@ contract DLE is ERC20, Governor, TimelockController {
|
|||||||
|
|
||||||
### ✅ **Надежность**
|
### ✅ **Надежность**
|
||||||
- Временем проверенные решения
|
- Временем проверенные решения
|
||||||
- Простая логика мультиподписи токен-холдеров
|
- Простая логика коллективного голосования токен‑холдеров
|
||||||
- Понятные механизмы таймлоков
|
- Понятные механизмы таймлоков
|
||||||
|
|
||||||
### ✅ **Совместимость**
|
### ✅ **Совместимость**
|
||||||
- Стандартные интерфейсы Ethereum
|
- Стандартные интерфейсы Ethereum
|
||||||
- Совместимость с существующими кошельками
|
- Совместимость с существующими кошельками
|
||||||
- Легкая интеграция с DeFi протоколами
|
- Легкая интеграция с DeFi протоколами
|
||||||
|
|
||||||
|
### Примечание про ERC-4337 (опционально)
|
||||||
|
- Может использоваться в кошельках/окружении для UX (userOps), но не является частью ядра DLE v2.
|
||||||
@@ -85,7 +85,7 @@ export async function createProposal(dleAddress, proposalData) {
|
|||||||
|
|
||||||
// ABI для создания предложения
|
// ABI для создания предложения
|
||||||
const dleAbi = [
|
const dleAbi = [
|
||||||
"function createProposal(string memory _description, uint256 _duration, bytes memory _operation, uint256 _governanceChainId) external returns (uint256)"
|
"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 dle = new ethers.Contract(dleAddress, dleAbi, signer);
|
||||||
@@ -95,7 +95,9 @@ export async function createProposal(dleAddress, proposalData) {
|
|||||||
proposalData.description,
|
proposalData.description,
|
||||||
proposalData.duration,
|
proposalData.duration,
|
||||||
proposalData.operation,
|
proposalData.operation,
|
||||||
proposalData.governanceChainId
|
proposalData.governanceChainId,
|
||||||
|
proposalData.targetChains || [],
|
||||||
|
proposalData.timelockDelay || 0
|
||||||
);
|
);
|
||||||
|
|
||||||
// Ждем подтверждения транзакции
|
// Ждем подтверждения транзакции
|
||||||
|
|||||||
@@ -683,6 +683,61 @@
|
|||||||
<div class="preview-item">
|
<div class="preview-item">
|
||||||
<strong>💰 Общая стоимость:</strong> ~${{ totalDeployCost.toFixed(2) }}
|
<strong>💰 Общая стоимость:</strong> ~${{ totalDeployCost.toFixed(2) }}
|
||||||
</div>
|
</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>
|
</div>
|
||||||
|
|
||||||
<!-- Приватный ключ -->
|
<!-- Приватный ключ -->
|
||||||
@@ -903,6 +958,13 @@ const availableNetworks = ref([]);
|
|||||||
const isLoadingNetworks = ref(false);
|
const isLoadingNetworks = ref(false);
|
||||||
const totalDeployCost = ref(0);
|
const totalDeployCost = ref(0);
|
||||||
const predictedAddress = ref('');
|
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);
|
const useSameKeyForAllChains = ref(true);
|
||||||
@@ -958,6 +1020,61 @@ const hasSelectedNetworks = computed(() => {
|
|||||||
return selectedNetworks.value.length > 0;
|
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(() => {
|
const selectedTokenStandardInfo = computed(() => {
|
||||||
return tokenStandardsData[dleSettings.tokenStandard] || null;
|
return tokenStandardsData[dleSettings.tokenStandard] || null;
|
||||||
@@ -2306,7 +2423,8 @@ const deploySmartContracts = async () => {
|
|||||||
currentChainId: dleSettings.selectedNetworks[0] || 1,
|
currentChainId: dleSettings.selectedNetworks[0] || 1,
|
||||||
|
|
||||||
// Приватный ключ для деплоя
|
// Приватный ключ для деплоя
|
||||||
privateKey: unifiedPrivateKey.value
|
privateKey: unifiedPrivateKey.value,
|
||||||
|
explorerApiKeys: explorerApiKeys
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('Данные для деплоя DLE:', deployData);
|
console.log('Данные для деплоя DLE:', deployData);
|
||||||
@@ -2333,6 +2451,12 @@ const deploySmartContracts = async () => {
|
|||||||
// Перенаправляем на главную страницу управления
|
// Перенаправляем на главную страницу управления
|
||||||
router.push('/management');
|
router.push('/management');
|
||||||
}, 2000);
|
}, 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 {
|
} else {
|
||||||
showDeployProgress.value = false;
|
showDeployProgress.value = false;
|
||||||
@@ -2375,6 +2499,15 @@ const validateCoordinates = (coordinates) => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<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 {
|
.settings-panel {
|
||||||
padding: var(--block-padding);
|
padding: var(--block-padding);
|
||||||
background-color: var(--color-light);
|
background-color: var(--color-light);
|
||||||
|
|||||||
@@ -214,6 +214,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</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">
|
<div class="form-section">
|
||||||
<h5>🔗 Выбор цепочки для кворума</h5>
|
<h5>🔗 Выбор цепочки для кворума</h5>
|
||||||
@@ -239,7 +248,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</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">
|
<div class="form-section">
|
||||||
<h5>⚙️ Тип операции</h5>
|
<h5>⚙️ Тип операции</h5>
|
||||||
|
|
||||||
@@ -458,7 +483,22 @@
|
|||||||
</div>
|
</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 class="form-section">
|
<div class="form-section">
|
||||||
<h5>👁️ Предварительный просмотр</h5>
|
<h5>👁️ Предварительный просмотр</h5>
|
||||||
<div class="preview-card">
|
<div class="preview-card">
|
||||||
@@ -480,21 +520,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</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 для авторизованных пользователей -->
|
</div> <!-- Закрываем div для авторизованных пользователей -->
|
||||||
</div>
|
</div>
|
||||||
@@ -507,6 +532,11 @@ import { useRouter, useRoute } from 'vue-router';
|
|||||||
import { useAuthContext } from '@/composables/useAuth';
|
import { useAuthContext } from '@/composables/useAuth';
|
||||||
import BaseLayout from '../../components/BaseLayout.vue';
|
import BaseLayout from '../../components/BaseLayout.vue';
|
||||||
import { getDLEInfo, loadProposals, createProposal as createProposalAPI, voteForProposal as voteForProposalAPI, executeProposal as executeProposalAPI, getSupportedChains } from '../../utils/dle-contract.js';
|
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 wsClient from '../../utils/websocket.js';
|
||||||
import { ethers } from 'ethers';
|
import { ethers } from 'ethers';
|
||||||
|
|
||||||
@@ -552,6 +582,8 @@ const newProposal = ref({
|
|||||||
description: '',
|
description: '',
|
||||||
duration: 7,
|
duration: 7,
|
||||||
governanceChainId: null,
|
governanceChainId: null,
|
||||||
|
timelockHours: 0,
|
||||||
|
targetChains: [],
|
||||||
operationType: '',
|
operationType: '',
|
||||||
operationParams: {
|
operationParams: {
|
||||||
to: '',
|
to: '',
|
||||||
@@ -584,6 +616,7 @@ const isFormValid = computed(() => {
|
|||||||
newProposal.value.duration > 0 &&
|
newProposal.value.duration > 0 &&
|
||||||
newProposal.value.governanceChainId &&
|
newProposal.value.governanceChainId &&
|
||||||
newProposal.value.operationType &&
|
newProposal.value.operationType &&
|
||||||
|
newProposal.value.timelockHours >= 0 &&
|
||||||
validateOperationParams()
|
validateOperationParams()
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -981,7 +1014,9 @@ async function createProposal() {
|
|||||||
description: newProposal.value.description,
|
description: newProposal.value.description,
|
||||||
duration: newProposal.value.duration * 24 * 60 * 60, // конвертируем в секунды
|
duration: newProposal.value.duration * 24 * 60 * 60, // конвертируем в секунды
|
||||||
operation: operation,
|
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);
|
console.log('Предложение создано:', result);
|
||||||
|
|||||||
Reference in New Issue
Block a user