Skip to content

Commit

Permalink
feat: frax msig
Browse files Browse the repository at this point in the history
  • Loading branch information
pegahcarter committed Aug 15, 2024
1 parent 93b3fac commit e9e0313
Showing 1 changed file with 365 additions and 0 deletions.
365 changes: 365 additions & 0 deletions src/contracts/FraxMultiSigWallet.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,365 @@
pragma solidity ^0.8.20;

/// @title Multisignature wallet - Allows multiple parties to agree on transactions before execution.
/// @author Stefan George - <[email protected]>
/// @author Frax Finance - https://github.com/FraxFinance
contract FraxMultiSigWallet {
/*
* Events
*/
event Confirmation(address indexed sender, uint256 indexed transactionId);
event Revocation(address indexed sender, uint256 indexed transactionId);
event Submission(uint256 indexed transactionId);
event Execution(uint256 indexed transactionId);
event ExecutionFailure(uint256 indexed transactionId);
event OwnerAddition(address indexed owner);
event OwnerRemoval(address indexed owner);
event RequirementChange(uint256 required);

/*
* Constants
*/
uint256 public constant MAX_OWNER_COUNT = 50;

/*
* Storage
*/
mapping(uint256 => Transaction) public transactions;
mapping(uint256 => mapping(address => bool)) public confirmations;
mapping(address => bool) public isOwner;
address[] public owners;
uint256 public required;
uint256 public transactionCount;

struct Transaction {
address destination;
uint256 value;
bytes data;
bool executed;
}

/*
* Modifiers
*/
modifier onlyWallet() {
require(msg.sender == address(this), "oW");
_;
}

modifier ownerDoesNotExist(address owner) {
require(!isOwner[owner], "oDNE");
_;
}

modifier ownerExists(address owner) {
require(isOwner[owner], "oE");
_;
}

modifier transactionExists(uint256 transactionId) {
require(transactions[transactionId].destination != address(0), "tE");
_;
}

modifier confirmed(uint256 transactionId, address owner) {
require(confirmations[transactionId][owner], "c");
_;
}

modifier notConfirmed(uint256 transactionId, address owner) {
require(!confirmations[transactionId][owner], "nC");
_;
}

modifier notExecuted(uint256 transactionId) {
require(!transactions[transactionId].executed, "nE");
_;
}

modifier notNull(address _address) {
require(_address != address(0), "nN");
_;
}

modifier validRequirement(uint256 ownerCount, uint256 _required) {
require(ownerCount <= MAX_OWNER_COUNT && _required <= ownerCount && _required != 0 && ownerCount != 0, "vR");
_;
}

receive() external payable {}

fallback() external payable {}

/*
* Public functions
*/
/// @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, uint256 _required) {
require(
_owners.length <= MAX_OWNER_COUNT && _required <= _owners.length && _required != 0 && _owners.length != 0,
"vR"
);
for (uint256 i = 0; i < _owners.length; ) {
require(!isOwner[_owners[i]] && _owners[i] != address(0));
isOwner[_owners[i]] = true;
unchecked {
++i;
}
}
owners = _owners;
required = _required;
}

/// @dev Allows to add a new owner. Transaction has to be sent by wallet.
/// @param owner Address of new owner.
function addOwner(
address owner
) public onlyWallet ownerDoesNotExist(owner) notNull(owner) validRequirement(owners.length + 1, required) {
isOwner[owner] = true;
owners.push(owner);
emit OwnerAddition(owner);
}

/// @dev Allows to remove an owner. Transaction has to be sent by wallet.
/// @param owner Address of owner.
function removeOwner(address owner) public onlyWallet ownerExists(owner) {
isOwner[owner] = false;
for (uint256 i = 0; i < owners.length - 1; ) {
if (owners[i] == owner) {
owners[i] = owners[owners.length - 1];
owners.pop();
break;
}
unchecked {
++i;
}
}
if (required > owners.length) {
changeRequirement(owners.length);
}
emit OwnerRemoval(owner);
}

/// @dev Allows to replace an owner with a new owner. Transaction has to be sent by wallet.
/// @param owner Address of owner to be replaced.
/// @param newOwner Address of new owner.
function replaceOwner(
address owner,
address newOwner
) public onlyWallet ownerExists(owner) ownerDoesNotExist(newOwner) {
for (uint256 i = 0; i < owners.length; ) {
if (owners[i] == owner) {
owners[i] = newOwner;
break;
}
unchecked {
++i;
}
}
isOwner[owner] = false;
isOwner[newOwner] = true;
emit OwnerRemoval(owner);
emit OwnerAddition(newOwner);
}

/// @dev Allows to change the number of required confirmations. Transaction has to be sent by wallet.
/// @param _required Number of required confirmations.
function changeRequirement(uint256 _required) public onlyWallet validRequirement(owners.length, _required) {
required = _required;
emit RequirementChange(_required);
}

/// @dev Allows an owner to submit and confirm a transaction.
/// @param destination Transaction target address.
/// @param value Transaction ether value.
/// @param data Transaction data payload.
/// @return transactionId Returns transaction ID.
function submitTransaction(
address destination,
uint256 value,
bytes calldata data
) public returns (uint256 transactionId) {
transactionId = addTransaction(destination, value, data);
confirmTransaction(transactionId);
}

/// @dev Allows an owner to confirm a transaction.
/// @param transactionId Transaction ID.
function confirmTransaction(
uint256 transactionId
) public ownerExists(msg.sender) transactionExists(transactionId) notConfirmed(transactionId, msg.sender) {
confirmations[transactionId][msg.sender] = true;
emit Confirmation(msg.sender, transactionId);
executeTransaction(transactionId);
}

/// @dev Allows an owner to revoke a confirmation for a transaction.
/// @param transactionId Transaction ID.
function revokeConfirmation(
uint256 transactionId
) public ownerExists(msg.sender) confirmed(transactionId, msg.sender) notExecuted(transactionId) {
confirmations[transactionId][msg.sender] = false;
emit Revocation(msg.sender, transactionId);
}

/// @dev Allows anyone to execute a confirmed transaction.
/// @param transactionId Transaction ID.
function executeTransaction(
uint256 transactionId
) public ownerExists(msg.sender) confirmed(transactionId, msg.sender) notExecuted(transactionId) {
if (isConfirmed(transactionId)) {
Transaction storage txn = transactions[transactionId];
txn.executed = true;
(bool success, ) = txn.destination.call{ value: txn.value }(txn.data);
if (success) {
emit Execution(transactionId);
} else {
emit ExecutionFailure(transactionId);
txn.executed = false;
}
}
}

/// @dev Returns the confirmation status of a transaction.
/// @param transactionId Transaction ID.
/// @return yes Confirmation status.
function isConfirmed(uint256 transactionId) public view returns (bool yes) {
uint256 count = 0;
uint256 length = owners.length;
for (uint256 i = 0; i < length; ) {
if (confirmations[transactionId][owners[i]]) {
count += 1;
}
if (count == required) {
yes = true;
break;
}

unchecked {
++i;
}
}
}

/*
* Internal functions
*/
/// @dev Adds a new transaction to the transaction mapping, if transaction does not exist yet.
/// @param destination Transaction target address.
/// @param value Transaction ether value.
/// @param data Transaction data payload.
/// @return transactionId
function addTransaction(
address destination,
uint256 value,
bytes calldata data
) internal notNull(destination) returns (uint256 transactionId) {
transactionId = transactionCount;
transactions[transactionId] = Transaction({
destination: destination,
value: value,
data: data,
executed: false
});
transactionCount += 1;
emit Submission(transactionId);
}

/*
* Web3 call functions
*/
/// @dev Returns number of confirmations of a transaction.
/// @param transactionId Transaction ID.
/// @return count Number of confirmations.
function getConfirmationCount(uint256 transactionId) public view returns (uint256 count) {
for (uint256 i = 0; i < owners.length; ) {
if (confirmations[transactionId][owners[i]]) {
count += 1;
}
unchecked {
++i;
}
}
}

/// @dev Returns total number of transactions after filers are applied.
/// @param pending Include pending transactions.
/// @param executed Include executed transactions.
/// @return count Total number of transactions after filters are applied.
function getTransactionCount(bool pending, bool executed) public view returns (uint256 count) {
for (uint256 i = 0; i < transactionCount; ) {
if ((pending && !transactions[i].executed) || (executed && transactions[i].executed)) {
count += 1;
}
unchecked {
++i;
}
}
}

/// @dev Returns list of owners.
/// @return List of owner addresses.
function getOwners() public view returns (address[] memory) {
return owners;
}

/// @dev Returns array with owner addresses, which confirmed transaction.
/// @param transactionId Transaction ID.
/// @return _confirmations Returns array of owner addresses.
function getConfirmations(uint256 transactionId) public view returns (address[] memory _confirmations) {
address[] memory confirmationsTemp = new address[](owners.length);
uint256 count = 0;
uint256 i;
for (i = 0; i < owners.length; ) {
if (confirmations[transactionId][owners[i]]) {
confirmationsTemp[count] = owners[i];
count += 1;
}
unchecked {
++i;
}
}
_confirmations = new address[](count);
for (i = 0; i < count; ) {
_confirmations[i] = confirmationsTemp[i];
unchecked {
++i;
}
}
}

/// @dev Returns list of transaction IDs in defined range.
/// @param from Index start position of transaction array.
/// @param to Index end position of transaction array.
/// @param pending Include pending transactions.
/// @param executed Include executed transactions.
/// @return _transactionIds Returns array of transaction IDs.
function getTransactionIds(
uint256 from,
uint256 to,
bool pending,
bool executed
) public view returns (uint256[] memory _transactionIds) {
uint256[] memory transactionIdsTemp = new uint256[](transactionCount);
uint256 count = 0;
uint256 i;
for (i = 0; i < transactionCount; ) {
if ((pending && !transactions[i].executed) || (executed && transactions[i].executed)) {
transactionIdsTemp[count] = i;
count += 1;
}
unchecked {
++i;
}
}
_transactionIds = new uint256[](to - from);
for (i = from; i < to; ) {
_transactionIds[i - from] = transactionIdsTemp[i];
unchecked {
++i;
}
}
}
}

0 comments on commit e9e0313

Please sign in to comment.