From b73ec0a98508d7b7916cb7e1ac431f1d9f212bc7 Mon Sep 17 00:00:00 2001 From: ArtemZ Date: Wed, 5 Apr 2023 10:03:37 +0500 Subject: [PATCH 1/5] Fix sold install, add message to set PATH --- CHANGELOG.md | 4 ++++ src/controllers/sold/components.ts | 1 + src/controllers/sold/index.ts | 5 +++++ 3 files changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d6381f..a37d485 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,10 @@ All notable changes to this project will be documented in this file. - The `contracts/HelloWallet.sol` contract has been updated to require solc 0.67.0 or later to compile. - The sample contract created with `everdev sol create` now requires solc 0.67.0 or later to compile. +### Fixed + +- The `sold install` command did not list the installed version in the registry. + ## [1.6.0] - 2023-02-13 ### New diff --git a/src/controllers/sold/components.ts b/src/controllers/sold/components.ts index 0a99553..90c1b9d 100644 --- a/src/controllers/sold/components.ts +++ b/src/controllers/sold/components.ts @@ -5,5 +5,6 @@ const TOOL_FOLDER_NAME = "sold" export const components = { driver: new Component(TOOL_FOLDER_NAME, "sold", { isExecutable: true, + resolveVersionRegExp: /[^0-9]*([0-9.]+)/, }), } diff --git a/src/controllers/sold/index.ts b/src/controllers/sold/index.ts index 1a8944b..4ac657e 100644 --- a/src/controllers/sold/index.ts +++ b/src/controllers/sold/index.ts @@ -39,6 +39,11 @@ export const soldInstallCommand: Command = { args: [], async run(terminal: Terminal) { await Component.ensureInstalledAll(terminal, components) + terminal.log( + chalk.yellow( + "Do not forget to add `$HOME/.everdev/sold` to your PATH environment variable", + ), + ) }, } From 16823f81a613de671690bb26ca5631bb70efd3df Mon Sep 17 00:00:00 2001 From: ArtemZ Date: Thu, 4 May 2023 00:55:40 +0500 Subject: [PATCH 2/5] SDK-4606 Add SetcodeMultisigWalletV2 giver --- CHANGELOG.md | 6 + contracts/SetcodeMultisigWalletV2.abi.json | 155 +++++ contracts/SetcodeMultisigWalletV2.sol | 670 +++++++++++++++++++++ contracts/SetcodeMultisigWalletV2.tvc | Bin 0 -> 4286 bytes package.json | 2 +- src/__tests__/network.ts | 8 + src/controllers/network/giver.ts | 2 + src/core/known-contracts.ts | 3 + 8 files changed, 845 insertions(+), 1 deletion(-) create mode 100644 contracts/SetcodeMultisigWalletV2.abi.json create mode 100644 contracts/SetcodeMultisigWalletV2.sol create mode 100644 contracts/SetcodeMultisigWalletV2.tvc diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d6381f..b7f153e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to this project will be documented in this file. +## [1.7.0] - 2023-05-04 + +### New + +- Added new type of giver - SetcodeMultisigWalletV2 + ## [1.6.1] - 2023-03-28 ### New diff --git a/contracts/SetcodeMultisigWalletV2.abi.json b/contracts/SetcodeMultisigWalletV2.abi.json new file mode 100644 index 0000000..e176f48 --- /dev/null +++ b/contracts/SetcodeMultisigWalletV2.abi.json @@ -0,0 +1,155 @@ +{ + "ABI version": 2, + "version": "2.3", + "header": ["pubkey", "time", "expire"], + "functions": [ + { + "name": "constructor", + "inputs": [ + {"name":"owners","type":"uint256[]"}, + {"name":"reqConfirms","type":"uint8"}, + {"name":"lifetime","type":"uint32"} + ], + "outputs": [ + ] + }, + { + "name": "sendTransaction", + "inputs": [ + {"name":"dest","type":"address"}, + {"name":"value","type":"uint128"}, + {"name":"bounce","type":"bool"}, + {"name":"flags","type":"uint8"}, + {"name":"payload","type":"cell"} + ], + "outputs": [ + ] + }, + { + "name": "submitTransaction", + "inputs": [ + {"name":"dest","type":"address"}, + {"name":"value","type":"uint128"}, + {"name":"bounce","type":"bool"}, + {"name":"allBalance","type":"bool"}, + {"name":"payload","type":"cell"}, + {"name":"stateInit","type":"optional(cell)"} + ], + "outputs": [ + {"name":"transId","type":"uint64"} + ] + }, + { + "name": "confirmTransaction", + "inputs": [ + {"name":"transactionId","type":"uint64"} + ], + "outputs": [ + ] + }, + { + "name": "isConfirmed", + "inputs": [ + {"name":"mask","type":"uint32"}, + {"name":"index","type":"uint8"} + ], + "outputs": [ + {"name":"confirmed","type":"bool"} + ] + }, + { + "name": "getParameters", + "inputs": [ + ], + "outputs": [ + {"name":"maxQueuedTransactions","type":"uint8"}, + {"name":"maxCustodianCount","type":"uint8"}, + {"name":"expirationTime","type":"uint64"}, + {"name":"minValue","type":"uint128"}, + {"name":"requiredTxnConfirms","type":"uint8"}, + {"name":"requiredUpdConfirms","type":"uint8"} + ] + }, + { + "name": "getTransaction", + "inputs": [ + {"name":"transactionId","type":"uint64"} + ], + "outputs": [ + {"components":[{"name":"id","type":"uint64"},{"name":"confirmationsMask","type":"uint32"},{"name":"signsRequired","type":"uint8"},{"name":"signsReceived","type":"uint8"},{"name":"creator","type":"uint256"},{"name":"index","type":"uint8"},{"name":"dest","type":"address"},{"name":"value","type":"uint128"},{"name":"sendFlags","type":"uint16"},{"name":"payload","type":"cell"},{"name":"bounce","type":"bool"},{"name":"stateInit","type":"optional(cell)"}],"name":"trans","type":"tuple"} + ] + }, + { + "name": "getTransactions", + "inputs": [ + ], + "outputs": [ + {"components":[{"name":"id","type":"uint64"},{"name":"confirmationsMask","type":"uint32"},{"name":"signsRequired","type":"uint8"},{"name":"signsReceived","type":"uint8"},{"name":"creator","type":"uint256"},{"name":"index","type":"uint8"},{"name":"dest","type":"address"},{"name":"value","type":"uint128"},{"name":"sendFlags","type":"uint16"},{"name":"payload","type":"cell"},{"name":"bounce","type":"bool"},{"name":"stateInit","type":"optional(cell)"}],"name":"transactions","type":"tuple[]"} + ] + }, + { + "name": "getCustodians", + "inputs": [ + ], + "outputs": [ + {"components":[{"name":"index","type":"uint8"},{"name":"pubkey","type":"uint256"}],"name":"custodians","type":"tuple[]"} + ] + }, + { + "name": "submitUpdate", + "inputs": [ + {"name":"codeHash","type":"optional(uint256)"}, + {"name":"owners","type":"optional(uint256[])"}, + {"name":"reqConfirms","type":"optional(uint8)"}, + {"name":"lifetime","type":"optional(uint32)"} + ], + "outputs": [ + {"name":"updateId","type":"uint64"} + ] + }, + { + "name": "confirmUpdate", + "inputs": [ + {"name":"updateId","type":"uint64"} + ], + "outputs": [ + ] + }, + { + "name": "executeUpdate", + "inputs": [ + {"name":"updateId","type":"uint64"}, + {"name":"code","type":"optional(cell)"} + ], + "outputs": [ + ] + }, + { + "name": "getUpdateRequests", + "inputs": [ + ], + "outputs": [ + {"components":[{"name":"id","type":"uint64"},{"name":"index","type":"uint8"},{"name":"signs","type":"uint8"},{"name":"confirmationsMask","type":"uint32"},{"name":"creator","type":"uint256"},{"name":"codeHash","type":"optional(uint256)"},{"name":"custodians","type":"optional(uint256[])"},{"name":"reqConfirms","type":"optional(uint8)"},{"name":"lifetime","type":"optional(uint32)"}],"name":"updates","type":"tuple[]"} + ] + } + ], + "data": [ + ], + "events": [ + ], + "fields": [ + {"name":"_pubkey","type":"uint256"}, + {"name":"_timestamp","type":"uint64"}, + {"name":"_constructorFlag","type":"bool"}, + {"name":"m_ownerKey","type":"uint256"}, + {"name":"m_requestsMask","type":"uint256"}, + {"components":[{"name":"id","type":"uint64"},{"name":"confirmationsMask","type":"uint32"},{"name":"signsRequired","type":"uint8"},{"name":"signsReceived","type":"uint8"},{"name":"creator","type":"uint256"},{"name":"index","type":"uint8"},{"name":"dest","type":"address"},{"name":"value","type":"uint128"},{"name":"sendFlags","type":"uint16"},{"name":"payload","type":"cell"},{"name":"bounce","type":"bool"},{"name":"stateInit","type":"optional(cell)"}],"name":"m_transactions","type":"map(uint64,tuple)"}, + {"name":"m_custodians","type":"map(uint256,uint8)"}, + {"name":"m_custodianCount","type":"uint8"}, + {"components":[{"name":"id","type":"uint64"},{"name":"index","type":"uint8"},{"name":"signs","type":"uint8"},{"name":"confirmationsMask","type":"uint32"},{"name":"creator","type":"uint256"},{"name":"codeHash","type":"optional(uint256)"},{"name":"custodians","type":"optional(uint256[])"},{"name":"reqConfirms","type":"optional(uint8)"},{"name":"lifetime","type":"optional(uint32)"}],"name":"m_updateRequests","type":"map(uint64,tuple)"}, + {"name":"m_updateRequestsMask","type":"uint32"}, + {"name":"m_requiredVotes","type":"uint8"}, + {"name":"m_defaultRequiredConfirmations","type":"uint8"}, + {"name":"m_lifetime","type":"uint32"} + ] +} diff --git a/contracts/SetcodeMultisigWalletV2.sol b/contracts/SetcodeMultisigWalletV2.sol new file mode 100644 index 0000000..9387999 --- /dev/null +++ b/contracts/SetcodeMultisigWalletV2.sol @@ -0,0 +1,670 @@ +/* + Multisignature Wallet with setcode + Copyright (C) 2022 Ever Surf + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ +pragma ton-solidity >=0.66.0; +pragma AbiHeader expire; +pragma AbiHeader pubkey; +pragma AbiHeader time; + +/// @title Multisignature wallet 2.0 with setcode. +/// @author Ever Surf +contract MultisigWallet { + + /* + * Storage + */ + + struct Transaction { + // Transaction Id. + uint64 id; + // Transaction confirmations from custodians. + uint32 confirmationsMask; + // Number of required confirmations. + uint8 signsRequired; + // Number of confirmations already received. + uint8 signsReceived; + // Public key of custodian queued transaction. + uint256 creator; + // Index of custodian. + uint8 index; + // Recipient address. + address dest; + // Amount of nanoevers to transfer. + uint128 value; + // Flags for sending internal message (see SENDRAWMSG in TVM spec). + uint16 sendFlags; + // Payload used as body of outbound internal message. + TvmCell payload; + // Bounce flag for header of outbound internal message. + bool bounce; + // Smart contract image to deploy with internal message. + optional(TvmCell) stateInit; + } + + /// Request for code update + struct UpdateRequest { + // request id + uint64 id; + // index of custodian submitted request + uint8 index; + // number of confirmations from custodians + uint8 signs; + // confirmation binary mask + uint32 confirmationsMask; + // public key of custodian submitted request + uint256 creator; + // New wallet code hash or null if wallet code should not be changed. + optional(uint256) codeHash; + // New wallet custodians (pubkeys) + optional(uint256[]) custodians; + // New number of confirmations required to execute transaction + optional(uint8) reqConfirms; + // New unconfirmed transaction lifetime in seconds. + optional(uint32) lifetime; + } + + /* + * Constants + */ + uint8 constant MAX_QUEUED_REQUESTS = 5; + uint32 constant DEFAULT_LIFETIME = 3600; // lifetime is 1 hour + uint32 constant MIN_LIFETIME = 10; // 10 secs + uint8 constant MAX_CUSTODIAN_COUNT = 32; + uint constant MAX_CLEANUP_TXNS = 40; + + // Send flags. + // Forward fees for message will be paid from contract balance. + uint8 constant FLAG_PAY_FWD_FEE_FROM_BALANCE = 1; + // Ignore errors in action phase to avoid errors if balance is less than sending value. + uint8 constant FLAG_IGNORE_ERRORS = 2; + // Send all remaining balance. + uint8 constant FLAG_SEND_ALL_REMAINING = 128; + + /* + * Variables + */ + // Public key of custodian who deployed a contract. + uint256 m_ownerKey; + // Binary mask with custodian requests (max 32 custodians). + uint256 m_requestsMask; + // Dictionary of queued transactions waiting for confirmations. + mapping(uint64 => Transaction) m_transactions; + // Set of custodians, initiated in constructor, but they can be changed later. + mapping(uint256 => uint8) m_custodians; // pub_key -> custodian_index + // Read-only custodian count, initiated in constructor. + uint8 m_custodianCount; + // Set of update requests. + mapping (uint64 => UpdateRequest) m_updateRequests; + // Binary mask for storing update request counts from custodians. + // Every custodian can submit only one request. + uint32 m_updateRequestsMask; + // Minimal number of confirmations required to upgrade wallet code. + uint8 m_requiredVotes; + // Minimal number of confirmations needed to execute transaction. + uint8 m_defaultRequiredConfirmations; + // Unconfirmed transaction lifetime, in seconds. + uint32 m_lifetime; + + /* + Exception codes: + 100 - message sender is not a custodian; + 102 - transaction does not exist; + 103 - operation is already confirmed by this custodian; + 107 - input value is too low; + 108 - wallet should have only one custodian; + 110 - Too many custodians; + 113 - Too many requests for one custodian; + 115 - update request does not exist; + 116 - update request already confirmed by this custodian; + 117 - invalid number of custodians; + 119 - stored code hash and calculated code hash are not equal; + 120 - update request is not confirmed; + 121 - payload size is too big; + 122 - object is expired; + 124 - new custodians are not defined; + 125 - `code` argument should be null; + 126 - in case of internal deploy: only 1 custodian is allowed; + 127 - in case of internal deploy: custodian pubkey must be equal to tvm.pubkey; + */ + + /* + * Constructor + */ + + /// @dev Internal function called from constructor to initialize custodians. + function _initialize( + optional(uint256[]) ownersOpt, + uint8 reqConfirms, + uint32 lifetime + ) inline private { + if (ownersOpt.hasValue()) { + uint8 ownerCount = 0; + uint256[] owners = ownersOpt.get(); + if (owners.length == 0) { + owners.push(tvm.pubkey()); + } + m_ownerKey = owners[0]; + uint256 len = owners.length; + delete m_custodians; + for (uint256 i = 0; (i < len && ownerCount < MAX_CUSTODIAN_COUNT); i++) { + uint256 key = owners[i]; + if (!m_custodians.exists(key)) { + m_custodians[key] = ownerCount++; + } + } + m_custodianCount = ownerCount; + } + + m_defaultRequiredConfirmations = math.min(m_custodianCount, reqConfirms); + + m_requiredVotes = (m_custodianCount <= 2) ? m_custodianCount : ((m_custodianCount * 2 + 1) / 3); + + uint32 minLifetime = uint32(m_custodianCount) * MIN_LIFETIME; + if (lifetime == 0) { + m_lifetime = DEFAULT_LIFETIME; + } else { + m_lifetime = math.max(minLifetime, math.min(lifetime, uint32(now & 0xFFFFFFFF))); + } + } + + /// @dev Contract constructor. + /// @param owners Array of custodian keys. + /// @param reqConfirms Minimal number of confirmations required for executing transaction. + /// @param lifetime Unconfirmed transaction lifetime, in seconds. + constructor(uint256[] owners, uint8 reqConfirms, uint32 lifetime) public { + require(owners.length > 0 && owners.length <= MAX_CUSTODIAN_COUNT, 117); + // Allow to deploy from other smart contracts + if (msg.sender.value == 0) { + // external deploy + require(msg.pubkey() == tvm.pubkey(), 100); + } else { + // internal deploy, + // check security condition + require(owners.length == 1, 126); + require(owners[0] == tvm.pubkey(), 127); + } + tvm.accept(); + _initialize(owners, reqConfirms, lifetime); + } + + /* + * Inline helper macros + */ + + /// @dev Returns queued transaction count by custodian with defined index. + function _getMaskValue(uint256 mask, uint8 index) inline private pure returns (uint8) { + return uint8((mask >> (8 * uint256(index))) & 0xFF); + } + + /// @dev Increment queued transaction count by custodian with defined index. + function _incMaskValue(uint256 mask, uint8 index) inline private pure returns (uint256) { + return mask + (1 << (8 * uint256(index))); + } + + /// @dev Decrement queued transaction count by custodian with defined index. + function _decMaskValue(uint256 mask, uint8 index) inline private pure returns (uint256) { + return mask - (1 << (8 * uint256(index))); + } + + /// @dev Checks bit with defined index in the mask. + function _checkBit(uint32 mask, uint8 index) inline private pure returns (bool) { + return (mask & (uint32(1) << index)) != 0; + } + + /// @dev Checks if object is confirmed by custodian. + function _isConfirmed(uint32 mask, uint8 custodianIndex) inline private pure returns (bool) { + return _checkBit(mask, custodianIndex); + } + + function _isSubmitted(uint32 mask, uint8 custodianIndex) inline private pure returns (bool) { + return _checkBit(mask, custodianIndex); + } + + /// @dev Sets custodian confirmation bit in the mask. + function _setConfirmed(uint32 mask, uint8 custodianIndex) inline private pure returns (uint32) { + mask |= (uint32(1) << custodianIndex); + return mask; + } + + /// @dev Checks that custodian with supplied public key exists in custodian set. + function _findCustodian(uint256 senderKey) inline private view returns (uint8) { + optional(uint8) custodianIndex = m_custodians.fetch(senderKey); + require(custodianIndex.hasValue(), 100); + return custodianIndex.get(); + } + + /// @dev Generates new id for transaction. + function _generateId() inline private pure returns (uint64) { + return (uint64(now) << 32) | (tx.timestamp & 0xFFFFFFFF); + } + + /// @dev Returns timestamp after which transactions are treated as expired. + function _getExpirationBound() inline private view returns (uint64) { + return (uint64(now) - uint64(m_lifetime)) << 32; + } + + /// @dev Returns transfer flags according to input value and `allBalance` flag. + function _getSendFlags(uint128 value, bool allBalance) inline private pure returns (uint8, uint128) { + uint8 flags = FLAG_IGNORE_ERRORS | FLAG_PAY_FWD_FEE_FROM_BALANCE; + if (allBalance) { + flags = FLAG_IGNORE_ERRORS | FLAG_SEND_ALL_REMAINING; + value = 0; + } + return (flags, value); + } + + /* + * Public functions + */ + + /// @dev Allows custodian if she is the only owner of multisig to transfer funds with minimal fees. + /// @param dest Transfer target address. + /// @param value Amount of funds to transfer. + /// @param bounce Bounce flag. Set true to transfer funds to existing account, + /// set false to create new account. + /// @param flags `sendmsg` flags. + /// @param payload Tree of cells used as body of outbound internal message. + function sendTransaction( + address dest, + uint128 value, + bool bounce, + uint8 flags, + TvmCell payload + ) public view { + require(m_custodianCount == 1, 108); + require(msg.pubkey() == m_ownerKey, 100); + tvm.accept(); + dest.transfer(value, bounce, flags | FLAG_IGNORE_ERRORS, payload); + } + + /// @dev Allows custodians to submit new transaction. + /// @param dest Transfer target address. + /// @param value Nanoevers value to transfer. + /// @param bounce Bounce flag. Set true if need to transfer evers to existing account; set false to create new account. + /// @param allBalance Set true if need to transfer all remaining balance. + /// @param payload Tree of cells used as body of outbound internal message. + /// @param stateInit Smart contract image to deploy with internal message. + /// @return transId Transaction ID. + function submitTransaction( + address dest, + uint128 value, + bool bounce, + bool allBalance, + TvmCell payload, + optional(TvmCell) stateInit + ) public returns (uint64 transId) { + uint256 senderKey = msg.pubkey(); + uint8 index = _findCustodian(senderKey); + _removeExpiredTransactions(); + require(_getMaskValue(m_requestsMask, index) < MAX_QUEUED_REQUESTS, 113); + tvm.accept(); + + (uint8 flags, uint128 realValue) = _getSendFlags(value, allBalance); + + m_requestsMask = _incMaskValue(m_requestsMask, index); + uint64 trId = _generateId(); + Transaction txn = Transaction({ + id: trId, + confirmationsMask: 0, + signsRequired: m_defaultRequiredConfirmations, + signsReceived: 0, + creator: senderKey, + index: index, + dest: dest, + value: realValue, + sendFlags: flags, + payload: payload, + bounce: bounce, + stateInit: stateInit + }); + + _confirmTransaction(txn, index); + return trId; + } + + /// @dev Allows custodian to confirm a transaction. + /// @param transactionId Transaction ID. + function confirmTransaction(uint64 transactionId) public { + uint8 index = _findCustodian(msg.pubkey()); + _removeExpiredTransactions(); + optional(Transaction) txnOpt = m_transactions.fetch(transactionId); + require(txnOpt.hasValue(), 102); + Transaction txn = txnOpt.get(); + require(!_isConfirmed(txn.confirmationsMask, index), 103); + tvm.accept(); + _confirmTransaction(txn, index); + } + + /* + * Internal functions + */ + + /// @dev Confirms transaction by custodian with defined index. + /// @param txn Transaction object to confirm. + /// @param custodianIndex Ccustodian index which confirms transaction. + function _confirmTransaction( + Transaction txn, + uint8 custodianIndex + ) inline private { + if ((txn.signsReceived + 1) >= txn.signsRequired) { + if (txn.stateInit.hasValue()) { + txn.dest.transfer({ + value: txn.value, + bounce: txn.bounce, + flag: txn.sendFlags, + body: txn.payload, + stateInit: txn.stateInit.get() + }); + } else { + txn.dest.transfer({ + value: txn.value, + bounce: txn.bounce, + flag: txn.sendFlags, + body: txn.payload + }); + } + m_requestsMask = _decMaskValue(m_requestsMask, txn.index); + delete m_transactions[txn.id]; + } else { + txn.confirmationsMask = _setConfirmed(txn.confirmationsMask, custodianIndex); + txn.signsReceived++; + m_transactions[txn.id] = txn; + } + } + + /// @dev Removes expired transactions from storage. + function _removeExpiredTransactions() private { + uint64 marker = _getExpirationBound(); + if (m_transactions.empty()) return; + + (uint64 trId, Transaction txn) = m_transactions.min().get(); + bool needCleanup = trId <= marker; + + if (needCleanup) { + tvm.accept(); + uint i = 0; + while (needCleanup && i < MAX_CLEANUP_TXNS) { + i++; + // transaction is expired, remove it + m_requestsMask = _decMaskValue(m_requestsMask, txn.index); + delete m_transactions[trId]; + optional(uint64, Transaction) nextTxn = m_transactions.next(trId); + if (nextTxn.hasValue()) { + (trId, txn) = nextTxn.get(); + needCleanup = trId <= marker; + } else { + needCleanup = false; + } + } + tvm.commit(); + } + } + + /* + * Get methods + */ + + /// @dev Helper get-method for checking if custodian confirmation bit is set. + /// @return confirmed True if confirmation bit is set. + function isConfirmed(uint32 mask, uint8 index) external pure returns (bool confirmed) { + confirmed = _isConfirmed(mask, index); + } + + /// @dev Get-method that returns wallet configuration parameters. + /// @return maxQueuedTransactions The maximum number of unconfirmed transactions that a custodian can submit. + /// @return maxCustodianCount The maximum allowed number of wallet custodians. + /// @return expirationTime Transaction lifetime in seconds. + /// @return minValue The minimum value allowed to transfer in one transaction. + /// @return requiredTxnConfirms The minimum number of confirmations required to execute transaction. + /// @return requiredUpdConfirms The minimum number of confirmations required to update wallet code. + function getParameters() external view + returns (uint8 maxQueuedTransactions, + uint8 maxCustodianCount, + uint64 expirationTime, + uint128 minValue, + uint8 requiredTxnConfirms, + uint8 requiredUpdConfirms) { + + maxQueuedTransactions = MAX_QUEUED_REQUESTS; + maxCustodianCount = MAX_CUSTODIAN_COUNT; + expirationTime = m_lifetime; + minValue = 0; + requiredTxnConfirms = m_defaultRequiredConfirmations; + requiredUpdConfirms = m_requiredVotes; + } + + /// @dev Get-method that returns transaction info by id. + /// @return trans Transaction structure. + /// Throws exception if transaction does not exist. + function getTransaction(uint64 transactionId) external view + returns (Transaction trans) { + optional(Transaction) txnOpt = m_transactions.fetch(transactionId); + require(txnOpt.hasValue(), 102); + trans = txnOpt.get(); + } + + /// @dev Get-method that returns array of pending transactions. + /// Returns not expired transactions only. + /// @return transactions Array of queued transactions. + function getTransactions() external view returns (Transaction[] transactions) { + uint64 bound = _getExpirationBound(); + for ((uint64 id, Transaction txn): m_transactions) { + // returns only not expired transactions + if (id > bound) { + transactions.push(txn); + } + } + } + + /// @dev Helper structure to return information about custodian. + /// Used in getCustodians(). + struct CustodianInfo { + uint8 index; + uint256 pubkey; + } + + /// @dev Get-method that returns info about wallet custodians. + /// @return custodians Array of custodians. + function getCustodians() external view returns (CustodianInfo[] custodians) { + for ((uint256 key, uint8 index): m_custodians) { + custodians.push(CustodianInfo(index, key)); + } + } + + /* + SETCODE public functions + */ + + /// @dev Allows to submit update request. New custodians can be supplied. + /// @param codeHash New wallet code hash. + /// @param owners New wallet custodians (array of pubkeys). + /// @param reqConfirms New number of confirmations required for executing transaction. + /// @param lifetime New unconfirmed transaction lifetime, in seconds. + /// @return updateId Id of submitted update request. + function submitUpdate( + optional(uint256) codeHash, + optional(uint256[]) owners, + optional(uint8) reqConfirms, + optional(uint32) lifetime + ) public returns (uint64 updateId) { + uint256 sender = msg.pubkey(); + uint8 index = _findCustodian(sender); + if (owners.hasValue()) { + uint newOwnerCount = owners.get().length; + require(newOwnerCount > 0 && newOwnerCount <= MAX_CUSTODIAN_COUNT, 117); + } + _removeExpiredUpdateRequests(); + require(!_isSubmitted(m_updateRequestsMask, index), 113); + tvm.accept(); + + if (codeHash.hasValue()) { + if (codeHash.get() == tvm.hash(tvm.code())) { + codeHash.reset(); + } + } + m_updateRequestsMask = _setConfirmed(m_updateRequestsMask, index); + updateId = _generateId(); + m_updateRequests[updateId] = UpdateRequest({ + id: updateId, + index: index, + signs: 0, + confirmationsMask: 0, + creator: sender, + codeHash: codeHash, + custodians: owners, + reqConfirms: reqConfirms, + lifetime: lifetime + }); + _confirmUpdate(updateId, index); + } + + /// @dev Allow to confirm submitted update request. Call executeUpdate to do `setcode` + /// after necessary confirmation count. + /// @param updateId Id of submitted update request. + function confirmUpdate(uint64 updateId) public { + uint8 index = _findCustodian(msg.pubkey()); + _removeExpiredUpdateRequests(); + optional(UpdateRequest) requestOpt = m_updateRequests.fetch(updateId); + require(requestOpt.hasValue(), 115); + require(!_isConfirmed(requestOpt.get().confirmationsMask, index), 116); + tvm.accept(); + + _confirmUpdate(updateId, index); + } + + /// @dev Allows to execute confirmed update request. + /// @param updateId Id of update request. + /// @param code Root cell of tree of cells with contract code. + function executeUpdate(uint64 updateId, optional(TvmCell) code) public { + require(m_custodians.exists(msg.pubkey()), 100); + _removeExpiredUpdateRequests(); + optional(UpdateRequest) requestOpt = m_updateRequests.fetch(updateId); + require(requestOpt.hasValue(), 115); + UpdateRequest request = requestOpt.get(); + if (request.codeHash.hasValue()) { + require(code.hasValue(), 119); + require(tvm.hash(code.get()) == request.codeHash.get(), 119); + } else { + require(!code.hasValue(), 125); + } + require(request.signs >= m_requiredVotes, 120); + + tvm.accept(); + + _deleteUpdateRequest(updateId, request.index); + + tvm.commit(); + if (request.codeHash.hasValue()) { + TvmCell newcode = code.get(); + tvm.setcode(newcode); + tvm.setCurrentCode(newcode); + } + + TvmBuilder data; + if (request.custodians.hasValue()) { + data.store(true, request.custodians.get()); + } else { + data.store(false, m_custodians, m_custodianCount, m_ownerKey); + } + if (request.reqConfirms.hasValue()) { + data.store(request.reqConfirms.get()); + } else { + data.store(m_defaultRequiredConfirmations); + } + if (request.lifetime.hasValue()) { + data.store(request.lifetime.get()); + } else { + data.store(m_lifetime); + } + onCodeUpgrade(data.toCell()); + } + + /// @dev Get-method to query all pending update requests. + function getUpdateRequests() external view returns (UpdateRequest[] updates) { + uint64 bound = _getExpirationBound(); + for ((uint64 updateId, UpdateRequest req): m_updateRequests) { + if (updateId > bound) { + updates.push(req); + } + } + } + + /// @dev Old handler after code update. For compatibility with old msig. + function onCodeUpgrade(uint256[] newOwners, uint8 reqConfirms) private functionID(2) { + tvm.resetStorage(); + _initialize(newOwners, reqConfirms, 0); + } + + /// @dev Handler after code update. + function onCodeUpgrade(TvmCell data) private functionID(3) { + tvm.resetStorage(); + optional(uint256[]) owners; + TvmSlice slice = data.toSlice(); + bool ownersAsArray = slice.decode(bool); + if (ownersAsArray) { + owners = slice.decode(uint256[]); + } else { + (m_custodians, m_custodianCount, m_ownerKey) = slice.decode( + mapping(uint256 => uint8), uint8, uint256); + } + + (uint8 reqConfirms, uint32 lifetime) = slice.decode(uint8, uint32); + _initialize(owners, reqConfirms, lifetime); + } + + /* + * Internal functions + */ + + /// @dev Internal function for update confirmation. + function _confirmUpdate(uint64 updateId, uint8 custodianIndex) inline private { + UpdateRequest request = m_updateRequests[updateId]; + request.signs++; + request.confirmationsMask = _setConfirmed(request.confirmationsMask, custodianIndex); + m_updateRequests[updateId] = request; + } + + /// @dev Removes expired update requests. + function _removeExpiredUpdateRequests() inline private { + uint64 marker = _getExpirationBound(); + if (m_updateRequests.empty()) return; + + (uint64 updateId, UpdateRequest req) = m_updateRequests.min().get(); + bool needCleanup = updateId <= marker; + if (needCleanup) { + tvm.accept(); + while (needCleanup) { + // request is expired, remove it + _deleteUpdateRequest(updateId, req.index); + optional(uint64, UpdateRequest) reqOpt = m_updateRequests.next(updateId); + if (reqOpt.hasValue()) { + (updateId, req) = reqOpt.get(); + needCleanup = updateId <= marker; + } else { + needCleanup = false; + } + } + tvm.commit(); + } + } + + /// @dev Helper function to correctly delete request. + function _deleteUpdateRequest(uint64 updateId, uint8 index) inline private { + m_updateRequestsMask &= ~(uint32(1) << index); + delete m_updateRequests[updateId]; + } +} diff --git a/contracts/SetcodeMultisigWalletV2.tvc b/contracts/SetcodeMultisigWalletV2.tvc new file mode 100644 index 0000000000000000000000000000000000000000..2419b75ee2cbdb92b5e75288bea807286421593f GIT binary patch literal 4286 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 literal 0 HcmV?d00001 diff --git a/package.json b/package.json index db3b8d5..946a1a8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "everdev", - "version": "1.6.1", + "version": "1.7.0", "description": "Everscale Dev Environment", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/src/__tests__/network.ts b/src/__tests__/network.ts index 9512b80..047371c 100644 --- a/src/__tests__/network.ts +++ b/src/__tests__/network.ts @@ -47,6 +47,14 @@ test("Add network giver by type", async () => { expect(new NetworkRegistry().get("se").giver?.name).toEqual( "SafeMultisigWallet", ) + await runCommand(consoleTerminal, "network giver", { + name: "se", + type: "SetcodeMultisigWalletV2", + signer: "alice", + }) + expect(new NetworkRegistry().get("se").giver?.name).toEqual( + "SetcodeMultisigWalletV2", + ) }) test("Add network giver error", async () => { diff --git a/src/controllers/network/giver.ts b/src/controllers/network/giver.ts index dcb63ef..4d4b96d 100644 --- a/src/controllers/network/giver.ts +++ b/src/controllers/network/giver.ts @@ -109,6 +109,8 @@ export class NetworkGiver implements AccountGiver { send = giverV2Send } else if (contract === KnownContracts.SetcodeMultisigWallet) { send = multisigSend + } else if (contract === KnownContracts.SetcodeMultisigWalletV2) { + send = multisigSend } else if (contract === KnownContracts.SafeMultisigWallet) { send = multisigSend } else { diff --git a/src/core/known-contracts.ts b/src/core/known-contracts.ts index 4050aa4..cd1e0b1 100644 --- a/src/core/known-contracts.ts +++ b/src/core/known-contracts.ts @@ -106,6 +106,7 @@ export const KnownContracts = { GiverV2: knownContract("GiverV2"), GiverV3: knownContract("GiverV3"), SetcodeMultisigWallet: knownContract("SetcodeMultisigWallet"), + SetcodeMultisigWalletV2: knownContract("SetcodeMultisigWalletV2"), SafeMultisigWallet: knownContract("SafeMultisigWallet"), } @@ -123,6 +124,8 @@ const KnownContractsByCodeHash: { [codeHash: string]: KnownContract } = { KnownContracts.SetcodeMultisigWallet, "207dc560c5956de1a2c1479356f8f3ee70a59767db2bf4788b1d61ad42cdad82": KnownContracts.SetcodeMultisigWallet, + d66d198766abdbe1253f3415826c946c371f5112552408625aeb0b31e0ef2df3: + KnownContracts.SetcodeMultisigWalletV2, "80d6c47c4a25543c9b397b71716f3fae1e2c5d247174c52e2c19bd896442b105": KnownContracts.SafeMultisigWallet, } From 687dbd4a7825f23883de8b1f836a9c5ef39f793d Mon Sep 17 00:00:00 2001 From: ArtemZ Date: Thu, 4 May 2023 13:19:10 +0500 Subject: [PATCH 3/5] Renamed SetcodeMultisigWalletV2 -> MsigV2 --- ...odeMultisigWalletV2.abi.json => MsigV2.abi.json} | 0 .../{SetcodeMultisigWalletV2.sol => MsigV2.sol} | 0 .../{SetcodeMultisigWalletV2.tvc => MsigV2.tvc} | Bin src/__tests__/network.ts | 6 ++---- src/controllers/network/giver.ts | 2 +- src/core/known-contracts.ts | 4 ++-- 6 files changed, 5 insertions(+), 7 deletions(-) rename contracts/{SetcodeMultisigWalletV2.abi.json => MsigV2.abi.json} (100%) rename contracts/{SetcodeMultisigWalletV2.sol => MsigV2.sol} (100%) rename contracts/{SetcodeMultisigWalletV2.tvc => MsigV2.tvc} (100%) diff --git a/contracts/SetcodeMultisigWalletV2.abi.json b/contracts/MsigV2.abi.json similarity index 100% rename from contracts/SetcodeMultisigWalletV2.abi.json rename to contracts/MsigV2.abi.json diff --git a/contracts/SetcodeMultisigWalletV2.sol b/contracts/MsigV2.sol similarity index 100% rename from contracts/SetcodeMultisigWalletV2.sol rename to contracts/MsigV2.sol diff --git a/contracts/SetcodeMultisigWalletV2.tvc b/contracts/MsigV2.tvc similarity index 100% rename from contracts/SetcodeMultisigWalletV2.tvc rename to contracts/MsigV2.tvc diff --git a/src/__tests__/network.ts b/src/__tests__/network.ts index 047371c..e62b789 100644 --- a/src/__tests__/network.ts +++ b/src/__tests__/network.ts @@ -49,12 +49,10 @@ test("Add network giver by type", async () => { ) await runCommand(consoleTerminal, "network giver", { name: "se", - type: "SetcodeMultisigWalletV2", + type: "MsigV2", signer: "alice", }) - expect(new NetworkRegistry().get("se").giver?.name).toEqual( - "SetcodeMultisigWalletV2", - ) + expect(new NetworkRegistry().get("se").giver?.name).toEqual("MsigV2") }) test("Add network giver error", async () => { diff --git a/src/controllers/network/giver.ts b/src/controllers/network/giver.ts index 4d4b96d..629f821 100644 --- a/src/controllers/network/giver.ts +++ b/src/controllers/network/giver.ts @@ -109,7 +109,7 @@ export class NetworkGiver implements AccountGiver { send = giverV2Send } else if (contract === KnownContracts.SetcodeMultisigWallet) { send = multisigSend - } else if (contract === KnownContracts.SetcodeMultisigWalletV2) { + } else if (contract === KnownContracts.MsigV2) { send = multisigSend } else if (contract === KnownContracts.SafeMultisigWallet) { send = multisigSend diff --git a/src/core/known-contracts.ts b/src/core/known-contracts.ts index cd1e0b1..e7c101e 100644 --- a/src/core/known-contracts.ts +++ b/src/core/known-contracts.ts @@ -106,7 +106,7 @@ export const KnownContracts = { GiverV2: knownContract("GiverV2"), GiverV3: knownContract("GiverV3"), SetcodeMultisigWallet: knownContract("SetcodeMultisigWallet"), - SetcodeMultisigWalletV2: knownContract("SetcodeMultisigWalletV2"), + MsigV2: knownContract("MsigV2"), SafeMultisigWallet: knownContract("SafeMultisigWallet"), } @@ -125,7 +125,7 @@ const KnownContractsByCodeHash: { [codeHash: string]: KnownContract } = { "207dc560c5956de1a2c1479356f8f3ee70a59767db2bf4788b1d61ad42cdad82": KnownContracts.SetcodeMultisigWallet, d66d198766abdbe1253f3415826c946c371f5112552408625aeb0b31e0ef2df3: - KnownContracts.SetcodeMultisigWalletV2, + KnownContracts.MsigV2, "80d6c47c4a25543c9b397b71716f3fae1e2c5d247174c52e2c19bd896442b105": KnownContracts.SafeMultisigWallet, } From 6cf656469b01afda85e14ca80d518d118c83f17e Mon Sep 17 00:00:00 2001 From: ArtemZ Date: Thu, 4 May 2023 13:34:14 +0500 Subject: [PATCH 4/5] Edit Changelog --- CHANGELOG.md | 2 +- docs/command-line-interface/network-tool.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7f153e..ea67468 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ All notable changes to this project will be documented in this file. ### New -- Added new type of giver - SetcodeMultisigWalletV2 +- Added new type of giver - MsigV2 ## [1.6.1] - 2023-03-28 diff --git a/docs/command-line-interface/network-tool.md b/docs/command-line-interface/network-tool.md index ce773a8..1341159 100644 --- a/docs/command-line-interface/network-tool.md +++ b/docs/command-line-interface/network-tool.md @@ -99,7 +99,7 @@ Options: --help, -h Show command usage --signer, -s Signer to be used with giver --value, -v Deploying account initial balance in nanotokens - --type, -t Type giver contract (GiverV1 | GiverV2 | GiverV3 | SafeMultisigWallet | SetcodeMultisigWallet) + --type, -t Type giver contract (GiverV1 | GiverV2 | GiverV3 | SafeMultisigWallet | MsigV2 | SetcodeMultisigWallet) ``` **Note:** The default signer and the initial balance value of 10 tokens will be used, unless otherwise specified through options. Also note, that some contracts may require a higher initial balance for successful deployment. DePool contract, for instance, requires a minimun of 21 tokens. From 85855a47b4a179ab620172b655ebd0d1d7bcd126 Mon Sep 17 00:00:00 2001 From: Sergei Voronezhskii Date: Thu, 4 May 2023 17:12:38 +0300 Subject: [PATCH 5/5] Version 1.7.0 --- CHANGELOG.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c304a24..70c1e50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ All notable changes to this project will be documented in this file. ### New - Added new type of giver - MsigV2 + +### Fixed + +- The `sold install` command did not list the installed version in the registry. ## [1.6.1] - 2023-03-28 @@ -22,10 +26,6 @@ All notable changes to this project will be documented in this file. - The `contracts/HelloWallet.sol` contract has been updated to require solc 0.67.0 or later to compile. - The sample contract created with `everdev sol create` now requires solc 0.67.0 or later to compile. -### Fixed - -- The `sold install` command did not list the installed version in the registry. - ## [1.6.0] - 2023-02-13 ### New