-
Notifications
You must be signed in to change notification settings - Fork 17
/
OwnbitMultiSigV8(BSC).sol
216 lines (190 loc) · 8.99 KB
/
OwnbitMultiSigV8(BSC).sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
pragma solidity >=0.8.0 <0.9.0;
// This is the ETH/ERC20/NFT multisig contract for Ownbit.
//
// For 2-of-3 multisig, to authorize a spend, two signtures must be provided by 2 of the 3 owners.
// To generate the message to be signed, provide the destination address and
// spend amount (in wei) to the generateMessageToSign method.
// The signatures must be provided as the (v, r, s) hex-encoded coordinates.
// The S coordinate must be 0x00 or 0x01 corresponding to 0x1b and 0x1c, respectively.
//
// WARNING: The generated message is only valid until the next spend is executed.
// after that, a new message will need to be calculated.
//
//
// Accident Protection MultiSig, rules:
//
// Participants must keep themselves active by submitting transactions.
// Not submitting any transaction within 3,000,000 ETH blocks (roughly 416 days) will be treated as wallet lost (i.e. accident happened),
// other participants can still spend the assets as along as: valid signing count >= Min(mininual required count, active owners).
//
// INFO: This contract is ERC20/ERC721/ERC1155 compatible.
// This contract can both receive ETH, ERC20 and NFT (ERC721/ERC1155) tokens.
// Last update time: 2023-06-04.
contract OwnbitMultiSig {
uint constant public MAX_OWNER_COUNT = 9;
uint constant public MAX_INACTIVE_TIME = 416 days;
uint constant public CHAINID = 56; //chainId for BNB Smart Chain
// The N addresses which control the funds in this contract. The
// owners of M of these addresses will need to both sign a message
// allowing the funds in this contract to be spent.
mapping(address => uint256) private ownerActiveTimeMap; //uint256 is the active timestamp(in secs) of this owner
address[] private owners;
uint private required;
// The contract nonce is not accessible to the contract so we
// implement a nonce-like variable for replay protection.
uint256 private spendNonce = 0;
// An event sent when funds are received.
event Funded(address from, uint value);
// An event sent when an spendAny is executed.
event Spent(address to, uint value);
modifier validRequirement(uint ownerCount, uint _required) {
require (ownerCount <= MAX_OWNER_COUNT
&& _required <= ownerCount
&& _required >= 1);
_;
}
/// @dev Contract constructor sets initial owners and required number of confirmations.
/// @param _owners List of initial owners.
/// @param _required Number of required confirmations.
constructor(address[] memory _owners, uint _required) validRequirement(_owners.length, _required) {
for (uint i = 0; i < _owners.length; i++) {
//onwer should be distinct, and non-zero
if (ownerActiveTimeMap[_owners[i]] > 0 || _owners[i] == address(0x0)) {
revert();
}
ownerActiveTimeMap[_owners[i]] = block.timestamp;
}
owners = _owners;
required = _required;
}
// The fallback function for this contract.
fallback() external payable {
if (msg.value > 0) {
emit Funded(msg.sender, msg.value);
}
}
// @dev Returns list of owners.
// @return List of owner addresses.
function getOwners() public view returns (address[] memory) {
return owners;
}
function getSpendNonce() public view returns (uint256) {
return spendNonce;
}
function getRequired() public view returns (uint) {
return required;
}
//return the active timestamp of this owner
function getOwnerActiveTime(address addr) public view returns (uint256) {
return ownerActiveTimeMap[addr];
}
// Generates the message to sign given the output destination address and amount.
// includes this contract's address and a nonce for replay protection.
// One option to independently verify: https://leventozturk.com/engineering/sha3/ and select keccak
function generateMessageToSign(address destination, uint256 value, bytes memory data) private view returns (bytes32) {
//the sequence must match generateMultiSigV3 in JS
bytes32 message = keccak256(abi.encodePacked(address(this), destination, value, data, spendNonce, CHAINID));
return message;
}
function _messageToRecover(address destination, uint256 value, bytes memory data) private view returns (bytes32) {
bytes32 hashedUnsignedMessage = generateMessageToSign(destination, value, data);
bytes memory prefix = "\x19Ethereum Signed Message:\n32";
return keccak256(abi.encodePacked(prefix, hashedUnsignedMessage));
}
//destination can be a normal address or a contract address, such as ERC20 contract address.
//value is the wei transferred to the destination.
//data for transfer ether: 0x
//data for transfer erc20 example: 0xa9059cbb000000000000000000000000ac6342a7efb995d63cc91db49f6023e95873d25000000000000000000000000000000000000000000000000000000000000003e8
//data for transfer erc721 example: 0x42842e0e00000000000000000000000097b65ad59c8c96f2dd786751e6279a1a6d34a4810000000000000000000000006cb33e7179860d24635c66850f1f6a5d4f8eee6d0000000000000000000000000000000000000000000000000000000000042134
//data can contain any data to be executed.
function spend(address destination, uint256 value, uint8[] memory vs, bytes32[] memory rs, bytes32[] memory ss, bytes calldata data) external {
require(destination != address(this), "Not allow sending to yourself");
require(_validSignature(destination, value, vs, rs, ss, data), "invalid signatures");
spendNonce = spendNonce + 1;
//transfer tokens from this contract to the destination address
(bool sent,) = destination.call{value: value}(data);
if (sent) {
emit Spent(destination, value);
}
}
//send a tx from the owner address to active the owner
//Allow the owner to transfer some ETH, although this is not necessary.
function active() external payable {
require(ownerActiveTimeMap[msg.sender] > 0, "Not an owner");
ownerActiveTimeMap[msg.sender] = block.timestamp;
}
function getRequiredWithoutInactive() public view returns (uint) {
uint activeOwner = 0;
for (uint i = 0; i < owners.length; i++) {
//if the owner is active
if (ownerActiveTimeMap[owners[i]] + MAX_INACTIVE_TIME >= block.timestamp) {
activeOwner++;
}
}
//active owners still equal or greater then required
if (activeOwner >= required) {
return required;
}
//active less than required, all active must sign
if (activeOwner >= 1) {
return activeOwner;
}
//at least one sign.
return 1;
}
// Confirm that the signature triplets (v1, r1, s1) (v2, r2, s2) ...
// authorize a spend of this contract's funds to the given destination address.
function _validSignature(address destination, uint256 value, uint8[] memory vs, bytes32[] memory rs, bytes32[] memory ss, bytes memory data) private returns (bool) {
require(vs.length == rs.length);
require(rs.length == ss.length);
require(vs.length <= owners.length);
require(vs.length >= getRequiredWithoutInactive());
bytes32 message = _messageToRecover(destination, value, data);
address[] memory addrs = new address[](vs.length);
for (uint i = 0; i < vs.length; i++) {
//recover the address associated with the public key from elliptic curve signature or return zero on error
addrs[i] = ecrecover(message, vs[i]+27, rs[i], ss[i]);
}
require(_distinctOwners(addrs));
_updateActiveTime(addrs); //update addrs' active timestamp
//check again, this is important to prevent inactive owners from stealing the money.
require(vs.length >= getRequiredWithoutInactive(), "Active owners updated after the call, please call active() before calling spend.");
return true;
}
// Confirm the addresses as distinct owners of this contract.
function _distinctOwners(address[] memory addrs) private view returns (bool) {
if (addrs.length > owners.length) {
return false;
}
for (uint i = 0; i < addrs.length; i++) {
//> 0 means one of the owner
if (ownerActiveTimeMap[addrs[i]] == 0) {
return false;
}
//address should be distinct
for (uint j = 0; j < i; j++) {
if (addrs[i] == addrs[j]) {
return false;
}
}
}
return true;
}
//update the active block number for those owners
function _updateActiveTime(address[] memory addrs) private {
for (uint i = 0; i < addrs.length; i++) {
//only update active timestamp for owners
if (ownerActiveTimeMap[addrs[i]] > 0) {
ownerActiveTimeMap[addrs[i]] = block.timestamp;
}
}
}
//support ERC721 safeTransferFrom
function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes calldata _data) external returns(bytes4) {
return bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"));
}
function onERC1155Received(address _operator, address _from, uint256 _id, uint256 _value, bytes calldata _data) external returns(bytes4) {
return bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"));
}
}