ваше сообщение коммита
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
"buildInfo": "../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
"buildInfo": "../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
{
|
||||
"_format": "hh-sol-artifact-1",
|
||||
"contractName": "Ownable",
|
||||
"sourceName": "@openzeppelin/contracts/access/Ownable.sol",
|
||||
"abi": [
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "OwnableInvalidOwner",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "account",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "OwnableUnauthorizedAccount",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "previousOwner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "newOwner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "OwnershipTransferred",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "owner",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "renounceOwnership",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "newOwner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "transferOwnership",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
}
|
||||
],
|
||||
"bytecode": "0x",
|
||||
"deployedBytecode": "0x",
|
||||
"linkReferences": {},
|
||||
"deployedLinkReferences": {}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
"buildInfo": "../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
"buildInfo": "../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
"buildInfo": "../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
"buildInfo": "../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
}
|
||||
@@ -1,529 +0,0 @@
|
||||
{
|
||||
"_format": "hh-sol-artifact-1",
|
||||
"contractName": "ERC20Permit",
|
||||
"sourceName": "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol",
|
||||
"abi": [
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "ECDSAInvalidSignature",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "length",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "ECDSAInvalidSignatureLength",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "s",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "ECDSAInvalidSignatureS",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "allowance",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "needed",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "ERC20InsufficientAllowance",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "sender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "balance",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "needed",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "ERC20InsufficientBalance",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "approver",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "ERC20InvalidApprover",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "receiver",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "ERC20InvalidReceiver",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "sender",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "ERC20InvalidSender",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "spender",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "ERC20InvalidSpender",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "deadline",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "ERC2612ExpiredSignature",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "signer",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "ERC2612InvalidSigner",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "account",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "currentNonce",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "InvalidAccountNonce",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "InvalidShortString",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "string",
|
||||
"name": "str",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"name": "StringTooLong",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Approval",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [],
|
||||
"name": "EIP712DomainChanged",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Transfer",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "DOMAIN_SEPARATOR",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "spender",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "allowance",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "approve",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "account",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "balanceOf",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "decimals",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint8",
|
||||
"name": "",
|
||||
"type": "uint8"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "eip712Domain",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bytes1",
|
||||
"name": "fields",
|
||||
"type": "bytes1"
|
||||
},
|
||||
{
|
||||
"internalType": "string",
|
||||
"name": "name",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"internalType": "string",
|
||||
"name": "version",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "chainId",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "verifyingContract",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "salt",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256[]",
|
||||
"name": "extensions",
|
||||
"type": "uint256[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "name",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "string",
|
||||
"name": "",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "nonces",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "deadline",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint8",
|
||||
"name": "v",
|
||||
"type": "uint8"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "r",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "s",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "permit",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "symbol",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "string",
|
||||
"name": "",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "totalSupply",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "transfer",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "transferFrom",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
}
|
||||
],
|
||||
"bytecode": "0x",
|
||||
"deployedBytecode": "0x",
|
||||
"linkReferences": {},
|
||||
"deployedLinkReferences": {}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
"buildInfo": "../../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
{
|
||||
"_format": "hh-sol-artifact-1",
|
||||
"contractName": "IERC20Permit",
|
||||
"sourceName": "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol",
|
||||
"abi": [
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "DOMAIN_SEPARATOR",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "nonces",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "deadline",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint8",
|
||||
"name": "v",
|
||||
"type": "uint8"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "r",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "s",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "permit",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
}
|
||||
],
|
||||
"bytecode": "0x",
|
||||
"deployedBytecode": "0x",
|
||||
"linkReferences": {},
|
||||
"deployedLinkReferences": {}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
"buildInfo": "../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
"buildInfo": "../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/f3f069df8eac5bd54bd69f81015dcedb.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../../../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
"buildInfo": "../../../../../build-info/0a8127733a4304f351c9edaa66ba7dc8.json"
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
4
backend/artifacts/contracts/DLE.sol/DLE.dbg.json
Normal file
4
backend/artifacts/contracts/DLE.sol/DLE.dbg.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../build-info/6575c8c945d0e94a5880cf71f96c424f.json"
|
||||
}
|
||||
3177
backend/artifacts/contracts/DLE.sol/DLE.json
Normal file
3177
backend/artifacts/contracts/DLE.sol/DLE.json
Normal file
File diff suppressed because one or more lines are too long
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"_format": "hh-sol-dbg-1",
|
||||
"buildInfo": "../../build-info/b0e3c4fc1ab092680ddeb940e9de1d86.json"
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
2356
backend/cache/solidity-files-cache.json
vendored
2356
backend/cache/solidity-files-cache.json
vendored
File diff suppressed because it is too large
Load Diff
@@ -1,30 +1,20 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";
|
||||
import "@openzeppelin/contracts/governance/Governor.sol";
|
||||
import "@openzeppelin/contracts/governance/extensions/GovernorSettings.sol";
|
||||
import "@openzeppelin/contracts/governance/extensions/GovernorCountingSimple.sol";
|
||||
import "@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol";
|
||||
import "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol";
|
||||
import "@openzeppelin/contracts/governance/TimelockController.sol";
|
||||
import "@openzeppelin/contracts/governance/utils/IVotes.sol";
|
||||
import "@openzeppelin/contracts/utils/Nonces.sol";
|
||||
|
||||
/**
|
||||
* @title DLE (Digital Legal Entity)
|
||||
* @dev Основной смарт-контракт DLE согласно требованиям SMART_CONTRACTS.md
|
||||
*
|
||||
* Функции:
|
||||
* - ERC-20 токен управления с мультиподписью
|
||||
* - Система голосования с кворумом
|
||||
* - Казначейские функции
|
||||
* - Коммуникационные функции
|
||||
* - Настраиваемые таймлоки
|
||||
* - Модульная система
|
||||
* @dev Основной контракт DLE с отдельным модулем TimelockController.
|
||||
*/
|
||||
contract DLE is
|
||||
ERC20Permit,
|
||||
ERC20Votes,
|
||||
Governor,
|
||||
GovernorSettings,
|
||||
@@ -32,184 +22,210 @@ contract DLE is
|
||||
GovernorVotesQuorumFraction,
|
||||
GovernorTimelockControl
|
||||
{
|
||||
// Структура для хранения информации о DLE
|
||||
struct DLEInfo {
|
||||
string name;
|
||||
string symbol;
|
||||
string location;
|
||||
string[] isicCodes;
|
||||
string coordinates;
|
||||
uint256 jurisdiction;
|
||||
uint256 oktmo;
|
||||
string[] okvedCodes;
|
||||
uint256 kpp;
|
||||
uint256 creationTimestamp;
|
||||
bool isActive;
|
||||
}
|
||||
|
||||
// Структура для предложений
|
||||
struct Proposal {
|
||||
bytes operation; // Операция для выполнения
|
||||
uint256[] targetChains; // Целевые сети для исполнения
|
||||
uint256 timelock; // Время исполнения (timestamp)
|
||||
uint256 governanceChain; // Сеть где проходит голосование
|
||||
address initiator; // Инициатор предложения
|
||||
bytes[] signatures; // Подписи токен-холдеров
|
||||
bool executed; // Статус исполнения
|
||||
uint256 quorumRequired; // Требуемый кворум
|
||||
uint256 signaturesCount; // Количество собранных подписей
|
||||
struct DLEConfig {
|
||||
string name;
|
||||
string symbol;
|
||||
string location;
|
||||
string coordinates;
|
||||
uint256 jurisdiction;
|
||||
uint256 oktmo;
|
||||
string[] okvedCodes;
|
||||
uint256 kpp;
|
||||
uint48 votingDelay;
|
||||
uint32 votingPeriod;
|
||||
uint256 proposalThreshold;
|
||||
uint256 quorumPercentage;
|
||||
address[] initialPartners;
|
||||
uint256[] initialAmounts;
|
||||
}
|
||||
|
||||
struct Proposal {
|
||||
bytes operation;
|
||||
uint256[] targetChains;
|
||||
uint256 timelock;
|
||||
uint256 governanceChain;
|
||||
address initiator;
|
||||
bytes[] signatures;
|
||||
bool executed;
|
||||
uint256 quorumRequired;
|
||||
uint256 signaturesCount;
|
||||
}
|
||||
|
||||
struct TokenDistributionProposal {
|
||||
address[] partners;
|
||||
uint256[] amounts;
|
||||
uint256 timelock;
|
||||
address initiator;
|
||||
bytes[] signatures;
|
||||
bool executed;
|
||||
uint256 quorumRequired;
|
||||
uint256 signaturesCount;
|
||||
string description;
|
||||
}
|
||||
|
||||
struct TreasuryProposal {
|
||||
address recipient;
|
||||
uint256 amount;
|
||||
uint256 timelock;
|
||||
address initiator;
|
||||
bytes[] signatures;
|
||||
bool executed;
|
||||
uint256 quorumRequired;
|
||||
uint256 signaturesCount;
|
||||
string description;
|
||||
}
|
||||
|
||||
// Информация о DLE
|
||||
DLEInfo public dleInfo;
|
||||
|
||||
// Таймлок контроллер
|
||||
TimelockController public timelockController;
|
||||
|
||||
// Маппинг предложений
|
||||
mapping(uint256 => Proposal) public proposals;
|
||||
mapping(uint256 => TokenDistributionProposal) public tokenDistributionProposals;
|
||||
mapping(uint256 => TreasuryProposal) public treasuryProposals;
|
||||
uint256 public proposalCounter;
|
||||
|
||||
// Настройки кворума
|
||||
uint256 public tokenDistributionProposalCounter;
|
||||
uint256 public treasuryProposalCounter;
|
||||
uint256 public quorumPercentage;
|
||||
|
||||
// События
|
||||
bool public initialTokensDistributed = false;
|
||||
|
||||
// Казначейские функции
|
||||
mapping(address => uint256) public lastWithdrawalBlock; // Последний блок вывода для каждого адреса
|
||||
uint256 public totalTreasuryBalance; // Общий баланс казны
|
||||
|
||||
event DLEInitialized(
|
||||
string name,
|
||||
string symbol,
|
||||
string location,
|
||||
string coordinates,
|
||||
uint256 jurisdiction,
|
||||
uint256 oktmo,
|
||||
string[] okvedCodes,
|
||||
uint256 kpp,
|
||||
address tokenAddress,
|
||||
address timelockAddress,
|
||||
address governorAddress
|
||||
);
|
||||
|
||||
event TokensDistributed(address[] partners, uint256[] amounts);
|
||||
event InitialTokensDistributed(address[] partners, uint256[] amounts);
|
||||
event TokensDepositedToTreasury(address depositor, uint256 amount);
|
||||
event TreasuryProposalCreated(uint256 proposalId, address initiator, address recipient, uint256 amount, string description);
|
||||
event TreasuryProposalSigned(uint256 proposalId, address signer, uint256 signaturesCount);
|
||||
event TreasuryProposalExecuted(uint256 proposalId, address recipient, uint256 amount);
|
||||
event TokenDistributionProposalCreated(uint256 proposalId, address initiator, address[] partners, uint256[] amounts, string description);
|
||||
event TokenDistributionProposalSigned(uint256 proposalId, address signer, uint256 signaturesCount);
|
||||
event TokenDistributionProposalExecuted(uint256 proposalId, address[] partners, uint256[] amounts);
|
||||
event ProposalCreated(uint256 proposalId, address initiator, bytes operation);
|
||||
event ProposalSigned(uint256 proposalId, address signer, uint256 signaturesCount);
|
||||
event ProposalExecuted(uint256 proposalId);
|
||||
event ModuleInstalled(string moduleName, address moduleAddress);
|
||||
|
||||
/**
|
||||
* @dev Конструктор DLE
|
||||
* @param _name Название DLE
|
||||
* @param _symbol Символ токена управления
|
||||
* @param _location Местонахождение DLE
|
||||
* @param _isicCodes Коды деятельности ISIC
|
||||
* @param _votingDelay Задержка голосования в блоках
|
||||
* @param _votingPeriod Период голосования в блоках
|
||||
* @param _proposalThreshold Порог для создания предложений
|
||||
* @param _quorumPercentage Процент кворума
|
||||
* @param _minTimelockDelay Минимальная задержка таймлока в секундах
|
||||
*/
|
||||
constructor(
|
||||
string memory _name,
|
||||
string memory _symbol,
|
||||
string memory _location,
|
||||
string[] memory _isicCodes,
|
||||
uint48 _votingDelay,
|
||||
uint32 _votingPeriod,
|
||||
uint256 _proposalThreshold,
|
||||
uint256 _quorumPercentage,
|
||||
uint256 _minTimelockDelay
|
||||
DLEConfig memory config,
|
||||
address timelockAddress
|
||||
)
|
||||
ERC20(_name, _symbol)
|
||||
ERC20Permit(_name)
|
||||
Governor(_name)
|
||||
GovernorSettings(_votingDelay, _votingPeriod, _proposalThreshold)
|
||||
GovernorVotesQuorumFraction(_quorumPercentage)
|
||||
ERC20(config.name, config.symbol)
|
||||
Governor(config.name)
|
||||
GovernorSettings(config.votingDelay, config.votingPeriod, config.proposalThreshold)
|
||||
GovernorVotesQuorumFraction(config.quorumPercentage)
|
||||
GovernorTimelockControl(TimelockController(payable(timelockAddress)))
|
||||
GovernorVotes(IVotes(address(this)))
|
||||
{
|
||||
// Инициализируем информацию о DLE
|
||||
dleInfo = DLEInfo({
|
||||
name: _name,
|
||||
symbol: _symbol,
|
||||
location: _location,
|
||||
isicCodes: _isicCodes,
|
||||
name: config.name,
|
||||
symbol: config.symbol,
|
||||
location: config.location,
|
||||
coordinates: config.coordinates,
|
||||
jurisdiction: config.jurisdiction,
|
||||
oktmo: config.oktmo,
|
||||
okvedCodes: config.okvedCodes,
|
||||
kpp: config.kpp,
|
||||
creationTimestamp: block.timestamp,
|
||||
isActive: true
|
||||
});
|
||||
quorumPercentage = config.quorumPercentage;
|
||||
|
||||
// Устанавливаем кворум
|
||||
quorumPercentage = _quorumPercentage;
|
||||
// Автоматически распределяем начальные токены партнерам при деплое
|
||||
require(config.initialPartners.length == config.initialAmounts.length, "Arrays length mismatch");
|
||||
require(config.initialPartners.length > 0, "No initial partners");
|
||||
|
||||
for (uint256 i = 0; i < config.initialPartners.length; i++) {
|
||||
require(config.initialPartners[i] != address(0), "Zero address");
|
||||
require(config.initialAmounts[i] > 0, "Zero amount");
|
||||
_mint(config.initialPartners[i], config.initialAmounts[i]);
|
||||
}
|
||||
|
||||
initialTokensDistributed = true;
|
||||
emit InitialTokensDistributed(config.initialPartners, config.initialAmounts);
|
||||
|
||||
// Создаем таймлок контроллер
|
||||
address[] memory proposers = new address[](1);
|
||||
address[] memory executors = new address[](1);
|
||||
proposers[0] = address(this); // DLE контракт может предлагать
|
||||
executors[0] = address(0); // Любой может выполнять
|
||||
|
||||
timelockController = new TimelockController(
|
||||
_minTimelockDelay,
|
||||
proposers,
|
||||
executors,
|
||||
address(0) // Нет админа для децентрализации
|
||||
emit DLEInitialized(
|
||||
config.name,
|
||||
config.symbol,
|
||||
config.location,
|
||||
config.coordinates,
|
||||
config.jurisdiction,
|
||||
config.oktmo,
|
||||
config.okvedCodes,
|
||||
config.kpp,
|
||||
address(this),
|
||||
timelockAddress,
|
||||
address(this)
|
||||
);
|
||||
|
||||
// Отказываемся от роли админа в таймлоке
|
||||
timelockController.renounceRole(timelockController.DEFAULT_ADMIN_ROLE(), address(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Распределяет начальные токены между партнерами
|
||||
* @dev Создать предложение на распределение токенов
|
||||
* @param _partners Массив адресов партнеров
|
||||
* @param _amounts Массив сумм токенов для каждого партнера
|
||||
* @param _timelock Время исполнения (timestamp)
|
||||
* @param _description Описание предложения
|
||||
*/
|
||||
function distributeInitialTokens(
|
||||
function createTokenDistributionProposal(
|
||||
address[] memory _partners,
|
||||
uint256[] memory _amounts
|
||||
) external {
|
||||
uint256[] memory _amounts,
|
||||
uint256 _timelock,
|
||||
string memory _description
|
||||
) external returns (uint256) {
|
||||
require(_partners.length == _amounts.length, "Arrays length mismatch");
|
||||
require(_partners.length > 0, "Empty arrays");
|
||||
require(_timelock > block.timestamp, "Invalid timelock");
|
||||
require(balanceOf(msg.sender) > 0, "Must hold tokens to create proposal");
|
||||
|
||||
uint256 totalSupply = 0;
|
||||
for (uint256 i = 0; i < _partners.length; i++) {
|
||||
require(_partners[i] != address(0), "Zero address");
|
||||
require(_amounts[i] > 0, "Zero amount");
|
||||
|
||||
totalSupply += _amounts[i];
|
||||
_mint(_partners[i], _amounts[i]);
|
||||
}
|
||||
|
||||
emit TokensDistributed(_partners, _amounts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Создает новое предложение
|
||||
* @param _operation Операция для выполнения
|
||||
* @param _targetChains Целевые сети для исполнения
|
||||
* @param _timelockDelay Задержка таймлока
|
||||
* @return proposalId ID созданного предложения
|
||||
*/
|
||||
function createProposal(
|
||||
bytes calldata _operation,
|
||||
uint256[] calldata _targetChains,
|
||||
uint256 _timelockDelay
|
||||
) external onlyTokenHolder returns (uint256 proposalId) {
|
||||
require(_operation.length > 0, "Empty operation");
|
||||
require(_targetChains.length > 0, "No target chains");
|
||||
require(_timelockDelay > 0, "Invalid timelock");
|
||||
|
||||
proposalId = proposalCounter++;
|
||||
uint256 proposalId = tokenDistributionProposalCounter++;
|
||||
|
||||
proposals[proposalId] = Proposal({
|
||||
operation: _operation,
|
||||
targetChains: _targetChains,
|
||||
timelock: block.timestamp + _timelockDelay,
|
||||
governanceChain: block.chainid,
|
||||
tokenDistributionProposals[proposalId] = TokenDistributionProposal({
|
||||
partners: _partners,
|
||||
amounts: _amounts,
|
||||
timelock: _timelock,
|
||||
initiator: msg.sender,
|
||||
signatures: new bytes[](0),
|
||||
executed: false,
|
||||
quorumRequired: (totalSupply() * quorumPercentage) / 100,
|
||||
signaturesCount: 0
|
||||
signaturesCount: 0,
|
||||
description: _description
|
||||
});
|
||||
|
||||
emit ProposalCreated(proposalId, msg.sender, _operation);
|
||||
emit TokenDistributionProposalCreated(proposalId, msg.sender, _partners, _amounts, _description);
|
||||
return proposalId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Подписывает предложение
|
||||
* @dev Подписать предложение на распределение токенов
|
||||
* @param _proposalId ID предложения
|
||||
*/
|
||||
function signProposal(uint256 _proposalId) external onlyTokenHolder {
|
||||
Proposal storage proposal = proposals[_proposalId];
|
||||
function signTokenDistributionProposal(uint256 _proposalId) external {
|
||||
TokenDistributionProposal storage proposal = tokenDistributionProposals[_proposalId];
|
||||
require(!proposal.executed, "Proposal already executed");
|
||||
require(block.timestamp < proposal.timelock, "Timelock expired");
|
||||
|
||||
require(block.timestamp < proposal.timelock, "Proposal expired");
|
||||
require(balanceOf(msg.sender) > 0, "No tokens to sign");
|
||||
|
||||
// Проверяем, что пользователь еще не подписал
|
||||
for (uint256 i = 0; i < proposal.signatures.length; i++) {
|
||||
require(
|
||||
@@ -219,110 +235,256 @@ contract DLE is
|
||||
);
|
||||
}
|
||||
|
||||
// Добавляем подпись
|
||||
proposal.signatures.push(abi.encode(msg.sender));
|
||||
proposal.signaturesCount += balanceOf(msg.sender);
|
||||
proposal.signatures.push(abi.encodePacked(msg.sender));
|
||||
proposal.signaturesCount++;
|
||||
|
||||
emit ProposalSigned(_proposalId, msg.sender, proposal.signaturesCount);
|
||||
emit TokenDistributionProposalSigned(_proposalId, msg.sender, proposal.signaturesCount);
|
||||
|
||||
// Проверяем, достигнут ли кворум
|
||||
if (proposal.signaturesCount >= proposal.quorumRequired) {
|
||||
proposal.executed = true;
|
||||
_executeTokenDistribution(_proposalId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Выполняет предложение
|
||||
* @dev Выполнить распределение токенов после достижения кворума
|
||||
* @param _proposalId ID предложения
|
||||
*/
|
||||
function executeProposal(uint256 _proposalId) external {
|
||||
Proposal storage proposal = proposals[_proposalId];
|
||||
function _executeTokenDistribution(uint256 _proposalId) internal {
|
||||
TokenDistributionProposal storage proposal = tokenDistributionProposals[_proposalId];
|
||||
require(proposal.executed, "Proposal not executed");
|
||||
require(proposal.signaturesCount >= proposal.quorumRequired, "Insufficient quorum");
|
||||
|
||||
for (uint256 i = 0; i < proposal.partners.length; i++) {
|
||||
require(proposal.partners[i] != address(0), "Zero address");
|
||||
require(proposal.amounts[i] > 0, "Zero amount");
|
||||
_mint(proposal.partners[i], proposal.amounts[i]);
|
||||
}
|
||||
|
||||
emit TokenDistributionProposalExecuted(_proposalId, proposal.partners, proposal.amounts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Выполнить предложение на распределение токенов после истечения таймлока
|
||||
* @param _proposalId ID предложения
|
||||
*/
|
||||
function executeTokenDistributionProposal(uint256 _proposalId) external {
|
||||
TokenDistributionProposal storage proposal = tokenDistributionProposals[_proposalId];
|
||||
require(!proposal.executed, "Proposal already executed");
|
||||
require(block.timestamp >= proposal.timelock, "Timelock not expired");
|
||||
require(proposal.signaturesCount >= proposal.quorumRequired, "Insufficient quorum");
|
||||
|
||||
proposal.executed = true;
|
||||
_executeTokenDistribution(_proposalId);
|
||||
}
|
||||
|
||||
// Здесь будет логика выполнения операции
|
||||
// В зависимости от типа операции
|
||||
function createProposal(
|
||||
bytes memory _operation,
|
||||
uint256[] memory _targetChains,
|
||||
uint256 _timelock,
|
||||
uint256 _governanceChain
|
||||
) external returns (uint256) {
|
||||
require(_operation.length > 0, "Empty operation");
|
||||
require(_targetChains.length > 0, "Empty target chains");
|
||||
require(_timelock > block.timestamp, "Invalid timelock");
|
||||
uint256 proposalId = proposalCounter++;
|
||||
proposals[proposalId] = Proposal({
|
||||
operation: _operation,
|
||||
targetChains: _targetChains,
|
||||
timelock: _timelock,
|
||||
governanceChain: _governanceChain,
|
||||
initiator: msg.sender,
|
||||
signatures: new bytes[](0),
|
||||
executed: false,
|
||||
quorumRequired: (totalSupply() * quorumPercentage) / 100,
|
||||
signaturesCount: 0
|
||||
});
|
||||
emit ProposalCreated(proposalId, msg.sender, _operation);
|
||||
return proposalId;
|
||||
}
|
||||
|
||||
emit ProposalExecuted(_proposalId);
|
||||
function signProposal(uint256 _proposalId) external {
|
||||
Proposal storage proposal = proposals[_proposalId];
|
||||
require(!proposal.executed, "Proposal already executed");
|
||||
require(block.timestamp < proposal.timelock, "Proposal expired");
|
||||
require(balanceOf(msg.sender) > 0, "No tokens to sign");
|
||||
proposal.signatures.push(abi.encodePacked(msg.sender));
|
||||
proposal.signaturesCount++;
|
||||
emit ProposalSigned(_proposalId, msg.sender, proposal.signaturesCount);
|
||||
if (proposal.signaturesCount >= proposal.quorumRequired) {
|
||||
proposal.executed = true;
|
||||
emit IGovernor.ProposalExecuted(_proposalId);
|
||||
}
|
||||
}
|
||||
|
||||
function installModule(string memory _moduleName, address _moduleAddress) external {
|
||||
emit ModuleInstalled(_moduleName, _moduleAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Получает информацию о DLE
|
||||
* @return Информация о DLE
|
||||
* @dev Внести токены в казну DLE
|
||||
* @param _amount Количество токенов для внесения
|
||||
*/
|
||||
function getDLEInfo() external view returns (DLEInfo memory) {
|
||||
return dleInfo;
|
||||
function depositToTreasury(uint256 _amount) external {
|
||||
require(_amount > 0, "Amount must be greater than 0");
|
||||
require(balanceOf(msg.sender) >= _amount, "Insufficient balance");
|
||||
|
||||
_transfer(msg.sender, address(this), _amount);
|
||||
totalTreasuryBalance += _amount;
|
||||
|
||||
emit TokensDepositedToTreasury(msg.sender, _amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Получает адрес таймлока
|
||||
* @return Адрес таймлок контроллера
|
||||
* @dev Создать предложение на вывод средств из казны
|
||||
* @param _recipient Адрес получателя
|
||||
* @param _amount Количество токенов для вывода
|
||||
* @param _timelock Время исполнения (timestamp)
|
||||
* @param _description Описание предложения
|
||||
*/
|
||||
function getTimelockAddress() external view returns (address) {
|
||||
return address(timelockController);
|
||||
function createTreasuryProposal(
|
||||
address _recipient,
|
||||
uint256 _amount,
|
||||
uint256 _timelock,
|
||||
string memory _description
|
||||
) external returns (uint256) {
|
||||
require(_recipient != address(0), "Zero address");
|
||||
require(_amount > 0, "Amount must be greater than 0");
|
||||
require(_amount <= totalTreasuryBalance, "Insufficient treasury balance");
|
||||
require(_timelock > block.timestamp, "Invalid timelock");
|
||||
require(balanceOf(msg.sender) > 0, "Must hold tokens to create proposal");
|
||||
|
||||
uint256 proposalId = treasuryProposalCounter++;
|
||||
|
||||
treasuryProposals[proposalId] = TreasuryProposal({
|
||||
recipient: _recipient,
|
||||
amount: _amount,
|
||||
timelock: _timelock,
|
||||
initiator: msg.sender,
|
||||
signatures: new bytes[](0),
|
||||
executed: false,
|
||||
quorumRequired: (totalSupply() * quorumPercentage) / 100,
|
||||
signaturesCount: 0,
|
||||
description: _description
|
||||
});
|
||||
|
||||
emit TreasuryProposalCreated(proposalId, msg.sender, _recipient, _amount, _description);
|
||||
return proposalId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Модификатор для проверки владения токенами
|
||||
* @dev Подписать предложение на вывод средств из казны
|
||||
* @param _proposalId ID предложения
|
||||
*/
|
||||
modifier onlyTokenHolder() {
|
||||
require(balanceOf(msg.sender) > 0, "Not a token holder");
|
||||
_;
|
||||
function signTreasuryProposal(uint256 _proposalId) external {
|
||||
TreasuryProposal storage proposal = treasuryProposals[_proposalId];
|
||||
require(!proposal.executed, "Proposal already executed");
|
||||
require(block.timestamp < proposal.timelock, "Proposal expired");
|
||||
require(balanceOf(msg.sender) > 0, "No tokens to sign");
|
||||
|
||||
// Проверяем, что пользователь еще не подписал
|
||||
for (uint256 i = 0; i < proposal.signatures.length; i++) {
|
||||
require(
|
||||
proposal.signatures[i].length == 0 ||
|
||||
abi.decode(proposal.signatures[i], (address)) != msg.sender,
|
||||
"Already signed"
|
||||
);
|
||||
}
|
||||
|
||||
proposal.signatures.push(abi.encodePacked(msg.sender));
|
||||
proposal.signaturesCount++;
|
||||
|
||||
emit TreasuryProposalSigned(_proposalId, msg.sender, proposal.signaturesCount);
|
||||
|
||||
// Проверяем, достигнут ли кворум
|
||||
if (proposal.signaturesCount >= proposal.quorumRequired) {
|
||||
proposal.executed = true;
|
||||
_executeTreasuryProposal(_proposalId);
|
||||
}
|
||||
}
|
||||
|
||||
// Переопределения, необходимые для корректной работы токена голосования
|
||||
function _update(address from, address to, uint256 amount) internal override(ERC20, ERC20Votes) {
|
||||
/**
|
||||
* @dev Выполнить предложение на вывод средств из казны
|
||||
* @param _proposalId ID предложения
|
||||
*/
|
||||
function _executeTreasuryProposal(uint256 _proposalId) internal {
|
||||
TreasuryProposal storage proposal = treasuryProposals[_proposalId];
|
||||
require(proposal.executed, "Proposal not executed");
|
||||
require(proposal.signaturesCount >= proposal.quorumRequired, "Insufficient quorum");
|
||||
require(proposal.amount <= totalTreasuryBalance, "Insufficient treasury balance");
|
||||
|
||||
totalTreasuryBalance -= proposal.amount;
|
||||
_transfer(address(this), proposal.recipient, proposal.amount);
|
||||
|
||||
emit TreasuryProposalExecuted(_proposalId, proposal.recipient, proposal.amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Выполнить предложение на вывод средств после истечения таймлока
|
||||
* @param _proposalId ID предложения
|
||||
*/
|
||||
function executeTreasuryProposal(uint256 _proposalId) external {
|
||||
TreasuryProposal storage proposal = treasuryProposals[_proposalId];
|
||||
require(!proposal.executed, "Proposal already executed");
|
||||
require(block.timestamp >= proposal.timelock, "Timelock not expired");
|
||||
require(proposal.signaturesCount >= proposal.quorumRequired, "Insufficient quorum");
|
||||
|
||||
proposal.executed = true;
|
||||
_executeTreasuryProposal(_proposalId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @dev Получить доступную для вывода сумму для адреса (пропорционально доле)
|
||||
* @param _address Адрес для проверки
|
||||
* @return Доступная сумма для вывода
|
||||
*/
|
||||
function getAvailableWithdrawal(address _address) public view returns (uint256) {
|
||||
uint256 userBalance = balanceOf(_address);
|
||||
if (userBalance == 0 || totalTreasuryBalance == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Пропорционально доле в общем количестве токенов
|
||||
uint256 userShare = (userBalance * totalTreasuryBalance) / totalSupply();
|
||||
return userShare;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Переопределения для совместимости с ERC-6372
|
||||
function CLOCK_MODE() public pure override(Governor, GovernorVotes, Votes) returns (string memory) {
|
||||
return "mode=blocknumber&from=default";
|
||||
}
|
||||
function clock() public view override(Governor, GovernorVotes, Votes) returns (uint48) {
|
||||
return uint48(block.number);
|
||||
}
|
||||
function _update(address from, address to, uint256 amount) internal override(ERC20Votes) {
|
||||
super._update(from, to, amount);
|
||||
}
|
||||
|
||||
function nonces(address owner) public view override(ERC20Permit, Nonces) returns (uint256) {
|
||||
function nonces(address owner) public view override(Nonces) returns (uint256) {
|
||||
return super.nonces(owner);
|
||||
}
|
||||
|
||||
// Переопределения для Governor
|
||||
function votingDelay()
|
||||
public
|
||||
view
|
||||
override(Governor, GovernorSettings)
|
||||
returns (uint256)
|
||||
{
|
||||
function name() public view override(ERC20, Governor) returns (string memory) {
|
||||
return super.name();
|
||||
}
|
||||
function votingDelay() public view override(Governor, GovernorSettings) returns (uint256) {
|
||||
return super.votingDelay();
|
||||
}
|
||||
|
||||
function votingPeriod()
|
||||
public
|
||||
view
|
||||
override(Governor, GovernorSettings)
|
||||
returns (uint256)
|
||||
{
|
||||
function votingPeriod() public view override(Governor, GovernorSettings) returns (uint256) {
|
||||
return super.votingPeriod();
|
||||
}
|
||||
|
||||
function quorum(uint256 blockNumber)
|
||||
public
|
||||
view
|
||||
override(Governor, GovernorVotesQuorumFraction)
|
||||
returns (uint256)
|
||||
{
|
||||
function quorum(uint256 blockNumber) public view override(Governor, GovernorVotesQuorumFraction) returns (uint256) {
|
||||
return super.quorum(blockNumber);
|
||||
}
|
||||
|
||||
function state(uint256 proposalId)
|
||||
public
|
||||
view
|
||||
override(Governor, GovernorTimelockControl)
|
||||
returns (ProposalState)
|
||||
{
|
||||
function state(uint256 proposalId) public view override(Governor, GovernorTimelockControl) returns (ProposalState) {
|
||||
return super.state(proposalId);
|
||||
}
|
||||
|
||||
function proposalThreshold()
|
||||
public
|
||||
view
|
||||
override(Governor, GovernorSettings)
|
||||
returns (uint256)
|
||||
{
|
||||
function proposalThreshold() public view override(Governor, GovernorSettings) returns (uint256) {
|
||||
return super.proposalThreshold();
|
||||
}
|
||||
|
||||
function _cancel(
|
||||
address[] memory targets,
|
||||
uint256[] memory values,
|
||||
@@ -331,34 +493,15 @@ contract DLE is
|
||||
) internal override(Governor, GovernorTimelockControl) returns (uint256) {
|
||||
return super._cancel(targets, values, calldatas, descriptionHash);
|
||||
}
|
||||
|
||||
function _executor()
|
||||
internal
|
||||
view
|
||||
override(Governor, GovernorTimelockControl)
|
||||
returns (address)
|
||||
{
|
||||
function _executor() internal view override(Governor, GovernorTimelockControl) returns (address) {
|
||||
return super._executor();
|
||||
}
|
||||
|
||||
function supportsInterface(bytes4 interfaceId)
|
||||
public
|
||||
view
|
||||
override(Governor)
|
||||
returns (bool)
|
||||
{
|
||||
function supportsInterface(bytes4 interfaceId) public view override(Governor) returns (bool) {
|
||||
return super.supportsInterface(interfaceId);
|
||||
}
|
||||
|
||||
function proposalNeedsQueuing(uint256 proposalId)
|
||||
public
|
||||
view
|
||||
override(Governor, GovernorTimelockControl)
|
||||
returns (bool)
|
||||
{
|
||||
function proposalNeedsQueuing(uint256 proposalId) public view override(Governor, GovernorTimelockControl) returns (bool) {
|
||||
return super.proposalNeedsQueuing(proposalId);
|
||||
}
|
||||
|
||||
function _queueOperations(
|
||||
uint256 proposalId,
|
||||
address[] memory targets,
|
||||
@@ -368,7 +511,6 @@ contract DLE is
|
||||
) internal override(Governor, GovernorTimelockControl) returns (uint48) {
|
||||
return super._queueOperations(proposalId, targets, values, calldatas, descriptionHash);
|
||||
}
|
||||
|
||||
function _executeOperations(
|
||||
uint256 proposalId,
|
||||
address[] memory targets,
|
||||
|
||||
@@ -64,10 +64,13 @@ class EmailBotService {
|
||||
user: settings.smtp_user,
|
||||
pass: settings.smtp_password,
|
||||
},
|
||||
pool: true,
|
||||
maxConnections: 3,
|
||||
maxMessages: 5,
|
||||
pool: false, // Отключаем пул соединений
|
||||
maxConnections: 1, // Ограничиваем до 1 соединения
|
||||
maxMessages: 1, // Ограничиваем до 1 сообщения на соединение
|
||||
tls: { rejectUnauthorized: false },
|
||||
connectionTimeout: 30000, // 30 секунд на подключение
|
||||
greetingTimeout: 30000, // 30 секунд на приветствие
|
||||
socketTimeout: 60000, // 60 секунд на операции сокета
|
||||
});
|
||||
}
|
||||
|
||||
@@ -142,8 +145,8 @@ class EmailBotService {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ищем непрочитанные письма
|
||||
this.imap.search(['UNSEEN'], (err, results) => {
|
||||
// Ищем все письма и проверяем их флаги вручную
|
||||
this.imap.search(['ALL'], (err, results) => {
|
||||
if (err) {
|
||||
logger.error(`Error searching messages: ${err}`);
|
||||
this.imap.end();
|
||||
@@ -151,173 +154,106 @@ class EmailBotService {
|
||||
}
|
||||
|
||||
if (!results || results.length === 0) {
|
||||
logger.info('No new messages found');
|
||||
logger.info('No messages found');
|
||||
this.imap.end();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const f = this.imap.fetch(results, { bodies: '' });
|
||||
// Фильтруем только непрочитанные сообщения
|
||||
const f = this.imap.fetch(results, {
|
||||
bodies: '',
|
||||
markSeen: true, // Помечаем как прочитанные
|
||||
struct: true // Получаем структуру для Message-ID
|
||||
});
|
||||
|
||||
f.on('message', (msg, seqno) => {
|
||||
msg.on('body', (stream, info) => {
|
||||
simpleParser(stream, async (err, parsed) => {
|
||||
if (err) {
|
||||
logger.error(`Error parsing message: ${err}`);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const fromEmail = parsed.from?.value?.[0]?.address;
|
||||
const subject = parsed.subject || '';
|
||||
const text = parsed.text || '';
|
||||
const html = parsed.html || '';
|
||||
// 1. Найти или создать пользователя
|
||||
const { userId, role } = await identityService.findOrCreateUserWithRole('email', fromEmail);
|
||||
if (await isUserBlocked(userId)) {
|
||||
logger.info(`Email от заблокированного пользователя ${userId} проигнорирован.`);
|
||||
return;
|
||||
}
|
||||
// 1.1 Найти или создать беседу
|
||||
let conversationResult = await encryptedDb.getData(
|
||||
'conversations',
|
||||
{ user_id: userId },
|
||||
1,
|
||||
'updated_at DESC, created_at DESC'
|
||||
);
|
||||
let conversation;
|
||||
if (conversationResult.length === 0) {
|
||||
const title = `Чат с пользователем ${userId}`;
|
||||
const newConv = await encryptedDb.saveData(
|
||||
'conversations',
|
||||
{ user_id: userId, title: title, created_at: new Date(), updated_at: new Date() }
|
||||
);
|
||||
conversation = newConv;
|
||||
} else {
|
||||
conversation = conversationResult[0];
|
||||
}
|
||||
|
||||
// Проверяем, что conversation создан успешно
|
||||
if (!conversation || !conversation.id) {
|
||||
logger.error(`[EmailBot] Conversation is undefined or has no id for user ${userId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Сохранять все сообщения с conversation_id
|
||||
let hasAttachments = parsed.attachments && parsed.attachments.length > 0;
|
||||
if (hasAttachments) {
|
||||
for (const att of parsed.attachments) {
|
||||
await encryptedDb.saveData(
|
||||
'messages',
|
||||
{
|
||||
user_id: userId,
|
||||
conversation_id: conversation.id,
|
||||
sender_type: 'user',
|
||||
content: text,
|
||||
channel: 'email',
|
||||
role: role,
|
||||
direction: 'in',
|
||||
created_at: new Date(),
|
||||
attachment_filename: att.filename,
|
||||
attachment_mimetype: att.contentType,
|
||||
attachment_size: att.size,
|
||||
attachment_data: att.content,
|
||||
metadata: JSON.stringify({ subject, html })
|
||||
}
|
||||
);
|
||||
}
|
||||
} else {
|
||||
await encryptedDb.saveData(
|
||||
'messages',
|
||||
{
|
||||
user_id: userId,
|
||||
conversation_id: conversation.id,
|
||||
sender_type: 'user',
|
||||
content: text,
|
||||
channel: 'email',
|
||||
role: role,
|
||||
direction: 'in',
|
||||
created_at: new Date(),
|
||||
metadata: JSON.stringify({ subject, html })
|
||||
}
|
||||
);
|
||||
}
|
||||
// 3. Получить ответ от ИИ (RAG + LLM)
|
||||
const aiSettings = await aiAssistantSettingsService.getSettings();
|
||||
let ragTableId = null;
|
||||
if (aiSettings && aiSettings.selected_rag_tables) {
|
||||
ragTableId = Array.isArray(aiSettings.selected_rag_tables)
|
||||
? aiSettings.selected_rag_tables[0]
|
||||
: aiSettings.selected_rag_tables;
|
||||
}
|
||||
let aiResponse;
|
||||
if (ragTableId) {
|
||||
// Сначала ищем ответ через RAG
|
||||
const ragResult = await ragAnswer({ tableId: ragTableId, userQuestion: text });
|
||||
if (ragResult && ragResult.answer && typeof ragResult.score === 'number' && Math.abs(ragResult.score) <= 0.3) {
|
||||
aiResponse = ragResult.answer;
|
||||
} else {
|
||||
aiResponse = await generateLLMResponse({
|
||||
userQuestion: text,
|
||||
context: ragResult && ragResult.context ? ragResult.context : '',
|
||||
answer: ragResult && ragResult.answer ? ragResult.answer : '',
|
||||
systemPrompt: aiSettings ? aiSettings.system_prompt : '',
|
||||
history: null,
|
||||
model: aiSettings ? aiSettings.model : undefined,
|
||||
language: aiSettings && aiSettings.languages && aiSettings.languages.length > 0 ? aiSettings.languages[0] : 'ru'
|
||||
});
|
||||
}
|
||||
} else {
|
||||
aiResponse = await aiAssistant.getResponse(text, 'auto');
|
||||
}
|
||||
if (await isUserBlocked(userId)) {
|
||||
logger.info(`[EmailBot] Пользователь ${userId} заблокирован — ответ ИИ не отправляется.`);
|
||||
return;
|
||||
}
|
||||
// 4. Сохранить ответ в БД с conversation_id
|
||||
await encryptedDb.saveData(
|
||||
'messages',
|
||||
{
|
||||
user_id: userId,
|
||||
conversation_id: conversation.id,
|
||||
sender_type: 'assistant',
|
||||
content: aiResponse,
|
||||
channel: 'email',
|
||||
role: role,
|
||||
direction: 'out',
|
||||
created_at: new Date(),
|
||||
metadata: JSON.stringify({ subject, html })
|
||||
}
|
||||
);
|
||||
// 5. Отправить ответ на email
|
||||
await this.sendEmail(fromEmail, 'Re: ' + subject, aiResponse);
|
||||
// После каждого успешного создания пользователя:
|
||||
broadcastContactsUpdate();
|
||||
} catch (processErr) {
|
||||
logger.error('Error processing incoming email:', processErr);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
let unreadMessages = [];
|
||||
let processedCount = 0;
|
||||
let totalMessages = results.length;
|
||||
|
||||
f.once('error', (err) => {
|
||||
logger.error(`Fetch error: ${err}`);
|
||||
});
|
||||
|
||||
f.once('end', () => {
|
||||
try {
|
||||
this.imap.end();
|
||||
} catch (e) {
|
||||
logger.error(`Error ending IMAP connection: ${e.message}`);
|
||||
f.on('message', (msg, seqno) => {
|
||||
let messageId = null;
|
||||
let uid = null;
|
||||
let flags = [];
|
||||
|
||||
// Получаем UID, Message-ID и флаги
|
||||
msg.once('attributes', (attrs) => {
|
||||
uid = attrs.uid;
|
||||
flags = attrs.flags || [];
|
||||
if (attrs['x-gm-msgid']) {
|
||||
messageId = attrs['x-gm-msgid'];
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error(`Error fetching messages: ${e.message}`);
|
||||
try {
|
||||
this.imap.end();
|
||||
} catch (e) {
|
||||
// Игнорируем ошибки при закрытии
|
||||
}
|
||||
}
|
||||
|
||||
msg.on('body', (stream, info) => {
|
||||
simpleParser(stream, async (err, parsed) => {
|
||||
if (err) {
|
||||
logger.error(`Error parsing message: ${err}`);
|
||||
processedCount++;
|
||||
if (processedCount >= totalMessages) {
|
||||
if (unreadMessages.length === 0) {
|
||||
logger.info('No unread messages found');
|
||||
}
|
||||
this.imap.end();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Получаем Message-ID из заголовков
|
||||
if (!messageId && parsed.messageId) {
|
||||
messageId = parsed.messageId;
|
||||
}
|
||||
|
||||
const fromEmail = parsed.from?.value?.[0]?.address;
|
||||
const subject = parsed.subject || '';
|
||||
const text = parsed.text || '';
|
||||
const html = parsed.html || '';
|
||||
|
||||
// Проверяем, что сообщение непрочитанное (нет флага \Seen)
|
||||
const isUnread = !flags.includes('\\Seen');
|
||||
|
||||
logger.info(`[EmailBot] Проверяем письмо: UID=${uid}, Message-ID=${messageId}, From=${fromEmail}, Unread=${isUnread}`);
|
||||
|
||||
if (isUnread) {
|
||||
unreadMessages.push({
|
||||
uid,
|
||||
messageId,
|
||||
fromEmail,
|
||||
subject,
|
||||
text,
|
||||
html,
|
||||
parsed
|
||||
});
|
||||
}
|
||||
|
||||
processedCount++;
|
||||
if (processedCount >= totalMessages) {
|
||||
if (unreadMessages.length === 0) {
|
||||
logger.info('No unread messages found');
|
||||
this.imap.end();
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info(`[EmailBot] Найдено ${unreadMessages.length} непрочитанных сообщений`);
|
||||
|
||||
// Обрабатываем каждое непрочитанное сообщение
|
||||
for (const messageData of unreadMessages) {
|
||||
try {
|
||||
await this.processIncomingEmail(messageData);
|
||||
} catch (processErr) {
|
||||
logger.error('Error processing incoming email:', processErr);
|
||||
}
|
||||
}
|
||||
|
||||
this.imap.end();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
f.once('error', (err) => {
|
||||
logger.error(`Error fetching messages: ${err}`);
|
||||
this.imap.end();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -334,22 +270,261 @@ class EmailBotService {
|
||||
}
|
||||
|
||||
// Метод для отправки email
|
||||
async sendEmail(to, subject, text) {
|
||||
async processIncomingEmail(messageData) {
|
||||
const { uid, messageId, fromEmail, subject, text, html, parsed } = messageData;
|
||||
|
||||
try {
|
||||
const settings = await this.getSettingsFromDb();
|
||||
const transporter = await this.getTransporter();
|
||||
const mailOptions = {
|
||||
from: settings.from_email,
|
||||
to,
|
||||
subject,
|
||||
text,
|
||||
};
|
||||
await transporter.sendMail(mailOptions);
|
||||
logger.info(`Email sent to ${to}`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.error('Error sending email:', error);
|
||||
throw error;
|
||||
logger.info(`[EmailBot] Обрабатываем письмо: UID=${uid}, Message-ID=${messageId}, From=${fromEmail}`);
|
||||
|
||||
// Фильтруем системные email адреса
|
||||
const systemEmails = [
|
||||
'mailer-daemon@smtp.hostland.ru',
|
||||
'mailer-daemon@',
|
||||
'noreply@',
|
||||
'no-reply@',
|
||||
'postmaster@',
|
||||
'bounce@',
|
||||
'daemon@'
|
||||
];
|
||||
|
||||
const isSystemEmail = systemEmails.some(systemEmail =>
|
||||
fromEmail && fromEmail.toLowerCase().includes(systemEmail.toLowerCase())
|
||||
);
|
||||
|
||||
if (isSystemEmail) {
|
||||
logger.info(`[EmailBot] Игнорируем системный email от ${fromEmail}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем, что email адрес валидный
|
||||
if (!fromEmail || !fromEmail.includes('@')) {
|
||||
logger.info(`[EmailBot] Игнорируем email с невалидным адресом: ${fromEmail}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем время письма - не обрабатываем письма старше 1 часа
|
||||
const emailDate = parsed.date || new Date();
|
||||
const now = new Date();
|
||||
const timeDiff = now.getTime() - emailDate.getTime();
|
||||
const hoursDiff = timeDiff / (1000 * 60 * 60);
|
||||
|
||||
if (hoursDiff > 1) {
|
||||
logger.info(`[EmailBot] Игнорируем старое письмо от ${fromEmail} (${hoursDiff.toFixed(1)} часов назад)`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем, не обрабатывали ли мы уже это письмо
|
||||
if (messageId) {
|
||||
const existingMessage = await encryptedDb.getData('messages', {
|
||||
metadata: { $like: `%"messageId":"${messageId}"%` }
|
||||
}, 1);
|
||||
|
||||
if (existingMessage.length > 0) {
|
||||
logger.info(`[EmailBot] Письмо с Message-ID ${messageId} уже обработано, пропускаем`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 1. Найти или создать пользователя
|
||||
const { userId, role } = await identityService.findOrCreateUserWithRole('email', fromEmail);
|
||||
if (await isUserBlocked(userId)) {
|
||||
logger.info(`Email от заблокированного пользователя ${userId} проигнорирован.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 1.1 Найти или создать беседу
|
||||
let conversationResult = await encryptedDb.getData(
|
||||
'conversations',
|
||||
{ user_id: userId },
|
||||
1,
|
||||
'updated_at DESC, created_at DESC'
|
||||
);
|
||||
let conversation;
|
||||
if (conversationResult.length === 0) {
|
||||
const title = `Чат с пользователем ${userId}`;
|
||||
const newConv = await encryptedDb.saveData(
|
||||
'conversations',
|
||||
{ user_id: userId, title: title, created_at: new Date(), updated_at: new Date() }
|
||||
);
|
||||
conversation = newConv;
|
||||
} else {
|
||||
conversation = conversationResult[0];
|
||||
}
|
||||
|
||||
// Проверяем, что conversation создан успешно
|
||||
if (!conversation || !conversation.id) {
|
||||
logger.error(`[EmailBot] Conversation is undefined or has no id for user ${userId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Сохранять все сообщения с conversation_id
|
||||
let hasAttachments = parsed.attachments && parsed.attachments.length > 0;
|
||||
if (hasAttachments) {
|
||||
for (const att of parsed.attachments) {
|
||||
await encryptedDb.saveData(
|
||||
'messages',
|
||||
{
|
||||
user_id: userId,
|
||||
conversation_id: conversation.id,
|
||||
sender_type: 'user',
|
||||
content: text,
|
||||
channel: 'email',
|
||||
role: role,
|
||||
direction: 'in',
|
||||
created_at: new Date(),
|
||||
attachment_filename: att.filename,
|
||||
attachment_mimetype: att.contentType,
|
||||
attachment_size: att.size,
|
||||
attachment_data: att.content,
|
||||
metadata: JSON.stringify({
|
||||
subject,
|
||||
html,
|
||||
messageId: messageId,
|
||||
uid: uid,
|
||||
fromEmail: fromEmail
|
||||
})
|
||||
}
|
||||
);
|
||||
}
|
||||
} else {
|
||||
await encryptedDb.saveData(
|
||||
'messages',
|
||||
{
|
||||
user_id: userId,
|
||||
conversation_id: conversation.id,
|
||||
sender_type: 'user',
|
||||
content: text,
|
||||
channel: 'email',
|
||||
role: role,
|
||||
direction: 'in',
|
||||
created_at: new Date(),
|
||||
metadata: JSON.stringify({
|
||||
subject,
|
||||
html,
|
||||
messageId: messageId,
|
||||
uid: uid,
|
||||
fromEmail: fromEmail
|
||||
})
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// 3. Получить ответ от ИИ (RAG + LLM)
|
||||
const aiSettings = await aiAssistantSettingsService.getSettings();
|
||||
let ragTableId = null;
|
||||
if (aiSettings && aiSettings.selected_rag_tables) {
|
||||
ragTableId = Array.isArray(aiSettings.selected_rag_tables)
|
||||
? aiSettings.selected_rag_tables[0]
|
||||
: aiSettings.selected_rag_tables;
|
||||
}
|
||||
let aiResponse;
|
||||
if (ragTableId) {
|
||||
// Сначала ищем ответ через RAG
|
||||
const ragResult = await ragAnswer({ tableId: ragTableId, userQuestion: text });
|
||||
if (ragResult && ragResult.answer && typeof ragResult.score === 'number' && Math.abs(ragResult.score) <= 0.3) {
|
||||
aiResponse = ragResult.answer;
|
||||
} else {
|
||||
aiResponse = await generateLLMResponse({
|
||||
userQuestion: text,
|
||||
context: ragResult && ragResult.context ? ragResult.context : '',
|
||||
answer: ragResult && ragResult.answer ? ragResult.answer : '',
|
||||
systemPrompt: aiSettings ? aiSettings.system_prompt : '',
|
||||
history: null,
|
||||
model: aiSettings ? aiSettings.model : undefined,
|
||||
language: aiSettings && aiSettings.languages && aiSettings.languages.length > 0 ? aiSettings.languages[0] : 'ru'
|
||||
});
|
||||
}
|
||||
} else {
|
||||
aiResponse = await aiAssistant.getResponse(text, 'auto');
|
||||
}
|
||||
|
||||
if (await isUserBlocked(userId)) {
|
||||
logger.info(`[EmailBot] Пользователь ${userId} заблокирован — ответ ИИ не отправляется.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. Сохранить ответ в БД с conversation_id
|
||||
await encryptedDb.saveData(
|
||||
'messages',
|
||||
{
|
||||
user_id: userId,
|
||||
conversation_id: conversation.id,
|
||||
sender_type: 'assistant',
|
||||
content: aiResponse,
|
||||
channel: 'email',
|
||||
role: role,
|
||||
direction: 'out',
|
||||
created_at: new Date(),
|
||||
metadata: JSON.stringify({
|
||||
subject,
|
||||
html,
|
||||
originalMessageId: messageId,
|
||||
originalUid: uid,
|
||||
originalFromEmail: fromEmail,
|
||||
isResponse: true
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
// 5. Отправить ответ на email
|
||||
try {
|
||||
await this.sendEmail(fromEmail, 'Re: ' + subject, aiResponse);
|
||||
logger.info(`[EmailBot] Email response sent successfully to ${fromEmail}`);
|
||||
} catch (emailError) {
|
||||
logger.error(`[EmailBot] Failed to send email response to ${fromEmail}:`, emailError);
|
||||
// Продолжаем выполнение, даже если email не отправлен
|
||||
}
|
||||
|
||||
// После каждого успешного создания пользователя:
|
||||
broadcastContactsUpdate();
|
||||
|
||||
} catch (processErr) {
|
||||
logger.error('Error processing incoming email:', processErr);
|
||||
}
|
||||
}
|
||||
|
||||
async sendEmail(to, subject, text) {
|
||||
const maxRetries = 3;
|
||||
const retryDelay = 5000; // 5 секунд между попытками
|
||||
|
||||
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||
try {
|
||||
const settings = await this.getSettingsFromDb();
|
||||
const transporter = await this.getTransporter();
|
||||
|
||||
const mailOptions = {
|
||||
from: settings.from_email,
|
||||
to,
|
||||
subject,
|
||||
text,
|
||||
};
|
||||
|
||||
await transporter.sendMail(mailOptions);
|
||||
logger.info(`Email sent to ${to} (attempt ${attempt})`);
|
||||
|
||||
// Закрываем соединение после успешной отправки
|
||||
transporter.close();
|
||||
return true;
|
||||
|
||||
} catch (error) {
|
||||
logger.error(`Error sending email (attempt ${attempt}/${maxRetries}):`, error);
|
||||
|
||||
// Если это последняя попытка, выбрасываем ошибку
|
||||
if (attempt === maxRetries) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Если ошибка связана с превышением лимита соединений, ждем дольше
|
||||
const isConnectionLimitError = error.message && (
|
||||
error.message.includes('too many connections') ||
|
||||
error.message.includes('421 4.7.0') ||
|
||||
error.message.includes('EPROTOCOL')
|
||||
);
|
||||
|
||||
const waitTime = isConnectionLimitError ? retryDelay * 2 : retryDelay;
|
||||
logger.info(`Waiting ${waitTime}ms before retry...`);
|
||||
await new Promise(resolve => setTimeout(resolve, waitTime));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user