Skip to content

Commit 1801380

Browse files
committed
update design docs & add Timelock contract
1 parent 5964b5b commit 1801380

File tree

4 files changed

+311
-6
lines changed

4 files changed

+311
-6
lines changed

DeGate Protocol Specification Document.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,8 @@ For more details, refer to [Circuit Design Document - Entire Merkle Tree](./Circ
3939
## No Limitation On Deposit/Withdrawal
4040
All funds deposited by users are stored in the smart contract of Layer 1, which can neither be transferred by any account in DeGate nor locked by the smart contract itself. Users can request to withdraw their funds at any time and complete their withdrawal within a set period of time.
4141

42-
## Certainty of Smart Contracts & Circuits
43-
DeGate smart contracts and circuits do not include any code upgrade logic. Once Smart Contracts & Circuits are deployed, no one can update them.
44-
42+
## Delayed Upgradable of Smart Contracts
43+
In order to enhance product functionality and safety, products can evolve and improve sustainably, DeGate smart contracts deployment adopts a delayed upgradable scheme. In the scheme, the admin rights to upgrade the contract are jointly managed by the Timelock contract and HomeDAO multi-signature account. When an upgrade occurs, DeGate users have at least 45 days to review the upcoming transaction and take action to decide whether to continue using DeGate, thus ensuring the highest level of trustless.
4544

4645
## Asset Independence
4746
Users can withdraw their assets from Layer 1 through Exodus Mode in the event that DeGate stops operation for any reason or accident.

Smart Contract Design.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ The validaty of conditional transactions is enforced by both zk-SNARKs and the s
7070

7171
A zk-SNARKs key pair is composed of a proving key and a verifying key. The proving key is used to generate proofs, while the verifying key is used for verifying proofs. Refer to "trusted setup" for more details about how these keys are generated.
7272

73-
On the off-chain side, the operators generate the proof through the circuit and proving key; On the on-chain side, the smart contract selects the corresponding verifying key by zkBlock size to verify proof. Different zkBlock sizes correspond to different verifying keys, and the keys are hardcoded into the smart contracts, so once deployed they cannot be changed. One zk-SNARKs key pair is unique to one circuit, meaning that changing keys will result in changing circuits. To make DeGate fully trustless, no verifying keys are allowed to be updated.
73+
On the off-chain side, the operators generate the proof through the circuit and proving key; On the on-chain side, the smart contract selects the corresponding verifying key by zkBlock size to verify proof. Different zkBlock sizes correspond to different verifying keys, and the keys are hardcoded into the smart contracts. One zk-SNARKs key pair is unique to one circuit, meaning that changing keys will result in changing circuits.
7474

7575

7676
Zk proofs are generated by operators, embedded inside zkBlock, then verified by smart contract using a hardcoded verifying key. To generate zero-knowledge proofs, DeGate takes Groth16 for zk-SNARKs, which is based on the ALT_BN128 curve. There are several precompiled opcodes ( ecAdd/ecMul/ecPairing, added in EIP-196 and EIP-197) for this curve that make verification efficient.
@@ -156,6 +156,8 @@ When using DeGate, users may need to pay different fees depending on the operati
156156

157157
For scenarios and fees,please refer to DeGate product documentation for details https://docs.degate.com/v/product_en/concepts/protocol-fees
158158

159+
## Delayed Upgradable of Smart Contracts
160+
In order to enhance product functionality and safety, products can evolve and improve sustainably, DeGate smart contracts deployment adopts a delayed upgradable scheme. In the scheme, the admin rights to upgrade the contract are jointly managed by the Timelock contract and HomeDAO multi-signature account. When an upgrade occurs, DeGate users have at least 45 days to review the upcoming transaction and take action to decide whether to continue using DeGate, thus ensuring the highest level of trustless.
159161

160162
# Contract Implementation
161163
## Overview
@@ -742,8 +744,7 @@ About gas cost for verification :
742744
## About Trustless
743745
User doesn't have to make any trust assumptions when using the DeGate contract, as "trustless" is guaranteed by the following factors:
744746

745-
- Contracts are immutable: DeGate does not use proxy mode, hence no change can be made after deployment.
746-
- Circuits are immutable: BlockVerifier contract does not allow verifying keys to be changed.
747+
- Contracts and circuits are upgradable with a delay. DeGate users have at least 45 days to review upcoming transactions and take action in advance.
747748
- Forced withdrawal can be made by users to force the operator to process the withdrawal request, otherwise, the exchange will shift into Exodus Mode.
748749
- Users can rebuild the whole Asset Merkle Tree by crawling Calldata in blocks.
749750
- In Exodus Mode, users can submit merkle proof of Asset Merkle Tree to withdraw funds from Layer 1 directly.
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
// SPDX-License-Identifier: BSD-3-Clause
2+
pragma solidity ^0.8.10;
3+
4+
// From https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/math/Math.sol
5+
// Subject to the MIT license.
6+
7+
/**
8+
* @dev Wrappers over Solidity's arithmetic operations with added overflow
9+
* checks.
10+
*
11+
* Arithmetic operations in Solidity wrap on overflow. This can easily result
12+
* in bugs, because programmers usually assume that an overflow raises an
13+
* error, which is the standard behavior in high level programming languages.
14+
* `SafeMath` restores this intuition by reverting the transaction when an
15+
* operation overflows.
16+
*
17+
* Using this library instead of the unchecked operations eliminates an entire
18+
* class of bugs, so it's recommended to use it always.
19+
*/
20+
library SafeMath {
21+
/**
22+
* @dev Returns the addition of two unsigned integers, reverting on overflow.
23+
*
24+
* Counterpart to Solidity's `+` operator.
25+
*
26+
* Requirements:
27+
* - Addition cannot overflow.
28+
*/
29+
function add(uint256 a, uint256 b) internal pure returns (uint256) {
30+
uint256 c;
31+
unchecked { c = a + b; }
32+
require(c >= a, "SafeMath: addition overflow");
33+
34+
return c;
35+
}
36+
37+
/**
38+
* @dev Returns the addition of two unsigned integers, reverting with custom message on overflow.
39+
*
40+
* Counterpart to Solidity's `+` operator.
41+
*
42+
* Requirements:
43+
* - Addition cannot overflow.
44+
*/
45+
function add(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
46+
uint256 c;
47+
unchecked { c = a + b; }
48+
require(c >= a, errorMessage);
49+
50+
return c;
51+
}
52+
53+
/**
54+
* @dev Returns the subtraction of two unsigned integers, reverting on underflow (when the result is negative).
55+
*
56+
* Counterpart to Solidity's `-` operator.
57+
*
58+
* Requirements:
59+
* - Subtraction cannot underflow.
60+
*/
61+
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
62+
return sub(a, b, "SafeMath: subtraction underflow");
63+
}
64+
65+
/**
66+
* @dev Returns the subtraction of two unsigned integers, reverting with custom message on underflow (when the result is negative).
67+
*
68+
* Counterpart to Solidity's `-` operator.
69+
*
70+
* Requirements:
71+
* - Subtraction cannot underflow.
72+
*/
73+
function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
74+
require(b <= a, errorMessage);
75+
uint256 c = a - b;
76+
77+
return c;
78+
}
79+
80+
/**
81+
* @dev Returns the multiplication of two unsigned integers, reverting on overflow.
82+
*
83+
* Counterpart to Solidity's `*` operator.
84+
*
85+
* Requirements:
86+
* - Multiplication cannot overflow.
87+
*/
88+
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
89+
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
90+
// benefit is lost if 'b' is also tested.
91+
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
92+
if (a == 0) {
93+
return 0;
94+
}
95+
96+
uint256 c;
97+
unchecked { c = a * b; }
98+
require(c / a == b, "SafeMath: multiplication overflow");
99+
100+
return c;
101+
}
102+
103+
/**
104+
* @dev Returns the multiplication of two unsigned integers, reverting on overflow.
105+
*
106+
* Counterpart to Solidity's `*` operator.
107+
*
108+
* Requirements:
109+
* - Multiplication cannot overflow.
110+
*/
111+
function mul(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
112+
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
113+
// benefit is lost if 'b' is also tested.
114+
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
115+
if (a == 0) {
116+
return 0;
117+
}
118+
119+
uint256 c;
120+
unchecked { c = a * b; }
121+
require(c / a == b, errorMessage);
122+
123+
return c;
124+
}
125+
126+
/**
127+
* @dev Returns the integer division of two unsigned integers.
128+
* Reverts on division by zero. The result is rounded towards zero.
129+
*
130+
* Counterpart to Solidity's `/` operator. Note: this function uses a
131+
* `revert` opcode (which leaves remaining gas untouched) while Solidity
132+
* uses an invalid opcode to revert (consuming all remaining gas).
133+
*
134+
* Requirements:
135+
* - The divisor cannot be zero.
136+
*/
137+
function div(uint256 a, uint256 b) internal pure returns (uint256) {
138+
return div(a, b, "SafeMath: division by zero");
139+
}
140+
141+
/**
142+
* @dev Returns the integer division of two unsigned integers.
143+
* Reverts with custom message on division by zero. The result is rounded towards zero.
144+
*
145+
* Counterpart to Solidity's `/` operator. Note: this function uses a
146+
* `revert` opcode (which leaves remaining gas untouched) while Solidity
147+
* uses an invalid opcode to revert (consuming all remaining gas).
148+
*
149+
* Requirements:
150+
* - The divisor cannot be zero.
151+
*/
152+
function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
153+
// Solidity only automatically asserts when dividing by 0
154+
require(b > 0, errorMessage);
155+
uint256 c = a / b;
156+
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
157+
158+
return c;
159+
}
160+
161+
/**
162+
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
163+
* Reverts when dividing by zero.
164+
*
165+
* Counterpart to Solidity's `%` operator. This function uses a `revert`
166+
* opcode (which leaves remaining gas untouched) while Solidity uses an
167+
* invalid opcode to revert (consuming all remaining gas).
168+
*
169+
* Requirements:
170+
* - The divisor cannot be zero.
171+
*/
172+
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
173+
return mod(a, b, "SafeMath: modulo by zero");
174+
}
175+
176+
/**
177+
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
178+
* Reverts with custom message when dividing by zero.
179+
*
180+
* Counterpart to Solidity's `%` operator. This function uses a `revert`
181+
* opcode (which leaves remaining gas untouched) while Solidity uses an
182+
* invalid opcode to revert (consuming all remaining gas).
183+
*
184+
* Requirements:
185+
* - The divisor cannot be zero.
186+
*/
187+
function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
188+
require(b != 0, errorMessage);
189+
return a % b;
190+
}
191+
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
// SPDX-License-Identifier: BSD-3-Clause
2+
// This code is taken from https://github.com/compound-finance/compound-protocol/blob/master/contracts/Timelock.sol
3+
4+
pragma solidity ^0.8.10;
5+
6+
import "./SafeMath.sol";
7+
8+
contract Timelock {
9+
using SafeMath for uint;
10+
11+
event NewAdmin(address indexed newAdmin);
12+
event NewPendingAdmin(address indexed newPendingAdmin);
13+
event NewDelay(uint indexed newDelay);
14+
event CancelTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint eta);
15+
event ExecuteTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint eta);
16+
event QueueTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint eta);
17+
18+
uint public constant GRACE_PERIOD = 7 days;
19+
uint public constant MINIMUM_DELAY = 45 days;
20+
uint public constant MAXIMUM_DELAY = 365 days;
21+
22+
address public admin;
23+
address public pendingAdmin;
24+
uint public delay;
25+
26+
mapping (bytes32 => bool) public queuedTransactions;
27+
28+
29+
constructor(address admin_, uint delay_) public {
30+
require(delay_ >= MINIMUM_DELAY, "Timelock::constructor: Delay must exceed minimum delay.");
31+
require(delay_ <= MAXIMUM_DELAY, "Timelock::setDelay: Delay must not exceed maximum delay.");
32+
33+
admin = admin_;
34+
delay = delay_;
35+
}
36+
37+
fallback() external payable { }
38+
39+
function setDelay(uint delay_) public {
40+
require(msg.sender == address(this), "Timelock::setDelay: Call must come from Timelock.");
41+
require(delay_ >= MINIMUM_DELAY, "Timelock::setDelay: Delay must exceed minimum delay.");
42+
require(delay_ <= MAXIMUM_DELAY, "Timelock::setDelay: Delay must not exceed maximum delay.");
43+
delay = delay_;
44+
45+
emit NewDelay(delay);
46+
}
47+
48+
function acceptAdmin() public {
49+
require(msg.sender == pendingAdmin, "Timelock::acceptAdmin: Call must come from pendingAdmin.");
50+
admin = msg.sender;
51+
pendingAdmin = address(0);
52+
53+
emit NewAdmin(admin);
54+
}
55+
56+
function setPendingAdmin(address pendingAdmin_) public {
57+
require(msg.sender == address(this), "Timelock::setPendingAdmin: Call must come from Timelock.");
58+
pendingAdmin = pendingAdmin_;
59+
60+
emit NewPendingAdmin(pendingAdmin);
61+
}
62+
63+
function queueTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) public returns (bytes32) {
64+
require(msg.sender == admin, "Timelock::queueTransaction: Call must come from admin.");
65+
require(eta >= getBlockTimestamp().add(delay), "Timelock::queueTransaction: Estimated execution block must satisfy delay.");
66+
67+
bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
68+
queuedTransactions[txHash] = true;
69+
70+
emit QueueTransaction(txHash, target, value, signature, data, eta);
71+
return txHash;
72+
}
73+
74+
function cancelTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) public {
75+
require(msg.sender == admin, "Timelock::cancelTransaction: Call must come from admin.");
76+
77+
bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
78+
queuedTransactions[txHash] = false;
79+
80+
emit CancelTransaction(txHash, target, value, signature, data, eta);
81+
}
82+
83+
function executeTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) public payable returns (bytes memory) {
84+
require(msg.sender == admin, "Timelock::executeTransaction: Call must come from admin.");
85+
86+
bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
87+
require(queuedTransactions[txHash], "Timelock::executeTransaction: Transaction hasn't been queued.");
88+
require(getBlockTimestamp() >= eta, "Timelock::executeTransaction: Transaction hasn't surpassed time lock.");
89+
require(getBlockTimestamp() <= eta.add(GRACE_PERIOD), "Timelock::executeTransaction: Transaction is stale.");
90+
91+
queuedTransactions[txHash] = false;
92+
93+
bytes memory callData;
94+
95+
if (bytes(signature).length == 0) {
96+
callData = data;
97+
} else {
98+
callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data);
99+
}
100+
101+
// solium-disable-next-line security/no-call-value
102+
(bool success, bytes memory returnData) = target.call{value: value}(callData);
103+
require(success, "Timelock::executeTransaction: Transaction execution reverted.");
104+
105+
emit ExecuteTransaction(txHash, target, value, signature, data, eta);
106+
107+
return returnData;
108+
}
109+
110+
function getBlockTimestamp() internal view returns (uint) {
111+
// solium-disable-next-line security/no-block-members
112+
return block.timestamp;
113+
}
114+
}

0 commit comments

Comments
 (0)