시나리오. 지갑 소유주는 3명이고 이중 2명이 허락해야 트랜잭션을 보낼 수 있다.
eth_sendTransaction
Transaction: 0x0627d6e709b8d0b226c0dd93a2bc01d803d79f2b516c4e659823927bc2528979
Contract created: 0x771c0fed5d0d203b34c644ff98ed7b084fd4db38
Gas usage: 2056951
Block Number: 1
Block Time: Mon Mar 27 2023 10:27:04 GMT+0900 (대한민국 표준시)
0:
address[]: 0xdc1f5172A3F7b184126e228c26379EB65564f866,0x6225bDB7E5c1C76Da3ba5C409D314390b47597ca,0xd6A77e56f22F213CD0E3a7F86f2fb846E1d3EcF4
let multisigWalletAddress = '0x771C0fEd5d0D203b34C644Ff98ED7B084Fd4DB38'
eth.sendTransaction({from:"0xE664DA6CDaBC6163b9542B210A2bE1a6885730d3", to:multiSigWallet, value: 1000});
multiSigWallet 컨트랙트 잔고 확인
truffle(development)> eth.getBalance(multiSigWallet)
'1000'
1번째 소유주가 “0x2c51Ce6bfa3CF47A9a6D3a30c4CDeb0F79Bf4D7E” 주소로 500 wei를 보내려고 한다.
truffle(development)> eth.getBalance(multiSigWallet)
'1000'
1번째 소유주인 유저가 SubmitTransaction()을 호출하여 트랜잭션을 생성한다.
{
"address destination": "0x2c51Ce6bfa3CF47A9a6D3a30c4CDeb0F79Bf4D7E",
"uint256 value": "500",
"bytes data": "0x00"
}
0:
address: destination 0x2c51Ce6bfa3CF47A9a6D3a30c4CDeb0F79Bf4D7E
1:
uint256: value 500
2:
bytes: data 0x00
3:
bool: executed false
2번째 소유주인 유저가 confirmTransaction()을 호출하여 1번째 유저가 생성한 트랜잭션을 허용한다.
결과 확인
truffle(development)> eth.getBalance(multiSigWallet)
'500'
truffle(development)> eth.getBalance('0x2c51Ce6bfa3CF47A9a6D3a30c4CDeb0F79Bf4D7E');
'100000000000000000500'
https://github.com/gnosis/MultiSigWallet/blob/master/contracts/MultiSigWallet.sol
function MultiSigWallet(address[] _owners, uint _required)
public
validRequirement(_owners.length, _required)
{
for (uint i=0; i<_owners.length; i++) {
require(!isOwner[_owners[i]] && _owners[i] != 0);
isOwner[_owners[i]] = true;
}
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);
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 (uint i=0; i<owners.length - 1; i++)
if (owners[i] == owner) {
owners[i] = owners[owners.length - 1];
break;
}
owners.length -= 1;
if (required > owners.length)
changeRequirement(owners.length);
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 (uint i=0; i<owners.length; i++)
if (owners[i] == owner) {
owners[i] = newOwner;
break;
}
isOwner[owner] = false;
isOwner[newOwner] = true;
OwnerRemoval(owner);
OwnerAddition(newOwner);
}
트랜잭션 실행에 필요한 _required 횟수를 변경할 수 있다.
/// @dev Allows to change the number of required confirmations. Transaction has to be sent by wallet.
/// @param _required Number of required confirmations.
function changeRequirement(uint _required)
public
onlyWallet
validRequirement(owners.length, _required)
{
required = _required;
RequirementChange(_required);
}
modifier validRequirement(uint ownerCount, uint _required) {
require(ownerCount <= MAX_OWNER_COUNT
&& _required <= ownerCount
&& _required != 0
&& ownerCount != 0);
_;
}
위 모든것이 충족되면 새로운 조건을 매개변수로하여 RequirementChange 이벤트를 호출한다.
소유자가 다른 소유자의 승인을 위해 새 트랜잭션을 생성하고 제출하는 함수
/// @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 Returns transaction ID.
function submitTransaction(address destination, uint value, bytes data)
public
returns (uint transactionId)
{
transactionId = addTransaction(destination, value, data);
confirmTransaction(transactionId);
}
소유자가 submitTransaction()을 사용하여 제출된 트랜잭션을 확인할 수 있도록 한다.
/// @dev Allows an owner to confirm a transaction.
/// @param transactionId Transaction ID.
function confirmTransaction(uint transactionId)
public
ownerExists(msg.sender)
transactionExists(transactionId)
notConfirmed(transactionId, msg.sender)
{
confirmations[transactionId][msg.sender] = true;
Confirmation(msg.sender, transactionId);
executeTransaction(transactionId);
}
multiSigWallet의 소유주가 특정 트랜잭션에 대한 확인을 취소할 수 있도록 하는 함수
소유주가 트랜잭션 확인은 했지만 마음이 바뀌면 해당 함수를 사용하여 취소하여 거래가 실행되지 않도록 할 수 있다.
/// @dev Allows an owner to revoke a confirmation for a transaction.
/// @param transactionId Transaction ID.
function revokeConfirmation(uint transactionId)
public
ownerExists(msg.sender)
confirmed(transactionId, msg.sender)
notExecuted(transactionId)
{
confirmations[transactionId][msg.sender] = false;
Revocation(msg.sender, transactionId);
}
확인된 트랜잭션을 실행하는 함수
/// @dev Allows anyone to execute a confirmed transaction.
/// @param transactionId Transaction ID.
function executeTransaction(uint transactionId)
public
ownerExists(msg.sender)
confirmed(transactionId, msg.sender)
notExecuted(transactionId)
{
if (isConfirmed(transactionId)) {
Transaction storage txn = transactions[transactionId];
txn.executed = true;
if (external_call(txn.destination, txn.value, txn.data.length, txn.data))
Execution(transactionId);
else {
ExecutionFailure(transactionId);
txn.executed = false;
}
}
}
저수준 어셈블리 코드를 사용하여 대상 컨트랙트에서 함수를 호출하고 호출이 성공했는지 여부를 나타내는 부울 값을 반환하는 함수
external_call 함수는 executeTransaction 함수에서 필요한 소유자 수에 의해 확인된 트랜잭션을 실행하는 데 사용됩니다.
// call has been separated into its own function in order to take advantage
// of the Solidity's code generator to produce a loop that copies tx.data into memory.
function external_call(address destination, uint value, uint dataLength, bytes data) internal returns (bool) {
bool result;
assembly {
let x := mload(0x40) // "Allocate" memory for output (0x40 is where "free memory" pointer is stored by convention)
let d := add(data, 32) // First 32 bytes are the padded length of data, so exclude that
result := call(
sub(gas, 34710), // 34710 is the value that solidity is currently emitting
// It includes callGas (700) + callVeryLow (3, to pay for SUB) + callValueTransferGas (9000) +
// callNewAccountGas (25000, in case the destination address does not exist and needs creating)
destination,
value,
d,
dataLength, // Size of the input (in bytes) - this is what fixes the padding problem
x,
0 // Output is ignored, therefore the output size is zero
)
}
return result;
}
함수 호출이 성공했는지 여부를 나타내는 부울 값을 반환한다.
let x := mload(0x40)
// 포인터 변수로 지정
let d := add(data, 32)
// 파라미터로 받은 data 32를 추가한다.
result := call(
sub(gas, 34710),
destination,
value,
d,
dataLength,
x,
0
)
주어진 거래가 필요한 수의 소유자에 의해 확인되었는지 확인하는 함수
/// @dev Returns the confirmation status of a transaction.
/// @param transactionId Transaction ID.
/// @return Confirmation status.
function isConfirmed(uint transactionId)
public
constant
returns (bool)
{
uint count = 0;
for (uint i=0; i<owners.length; i++) {
if (confirmations[transactionId][owners[i]])
count += 1;
if (count == required)
return true;
}
}
트랜잭션을 생성하는 함수
/*
* 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 Returns transaction ID.
function addTransaction(address destination, uint value, bytes data)
internal
notNull(destination)
returns (uint transactionId)
{
transactionId = transactionCount;
transactions[transactionId] = Transaction({
destination: destination,
value: value,
data: data,
executed: false
});
transactionCount += 1;
Submission(transactionId);
}
특정 트랜잭션에 대한 충족된 확인의 수를 반환하는 함수
/*
* Web3 call functions
*/
/// @dev Returns number of confirmations of a transaction.
/// @param transactionId Transaction ID.
/// @return Number of confirmations.
function getConfirmationCount(uint transactionId)
public
constant
returns (uint count)
{
for (uint i=0; i<owners.length; i++)
if (confirmations[transactionId][owners[i]])
count += 1;
}
owners에 등록된 만큼 반복문으로 반복하며 각 소유자에 대한 트랜잭션과 소유자에 대한 confirmations 매핑을 보고 해당 소유자가 트랜잭션을 확인했는지 확인하며 카운트를 증가시킨다.
지정된 필터를 적용하여 조건에 맞는 총 트랜잭션의 수를 반환하는 함수
/// @dev Returns total number of transactions after filers are applied.
/// @param pending Include pending transactions.
/// @param executed Include executed transactions.
/// @return Total number of transactions after filters are applied.
function getTransactionCount(bool pending, bool executed)
public
constant
returns (uint count)
{
for (uint i=0; i<transactionCount; i++)
if ( pending && !transactions[i].executed
|| executed && transactions[i].executed)
count += 1;
}
지갑 소유주의 주소들을 확인할 수 있다.
/// @dev Returns list of owners.
/// @return List of owner addresses.
function getOwners()
public
constant
returns (address[])
{
return owners;
}
트랜잭션을 확인한 소유자 주소의 배열을 반환하는 함수
/// @dev Returns array with owner addresses, which confirmed transaction.
/// @param transactionId Transaction ID.
/// @return Returns array of owner addresses.
function getConfirmations(uint transactionId)
public
constant
returns (address[] _confirmations)
{
address[] memory confirmationsTemp = new address[](owners.length);
uint count = 0;
uint i;
for (i=0; i<owners.length; i++)
if (confirmations[transactionId][owners[i]]) {
confirmationsTemp[count] = owners[i];
count += 1;
}
_confirmations = new address[](count);
for (i=0; i<count; i++)
_confirmations[i] = confirmationsTemp[i];
}
컨트랙트에 저장된 모든 트랜잭션 ID를 검색하는 데 사용된다.
/// @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 Returns array of transaction IDs.
function getTransactionIds(uint from, uint to, bool pending, bool executed)
public
constant
returns (uint[] _transactionIds)
{
uint[] memory transactionIdsTemp = new uint[](transactionCount);
uint count = 0;
uint i;
for (i=0; i<transactionCount; i++)
if ( pending && !transactions[i].executed
|| executed && transactions[i].executed)
{
transactionIdsTemp[count] = i;
count += 1;
}
_transactionIds = new uint[](to - from);
for (i=from; i<to; i++)
_transactionIds[i - from] = transactionIdsTemp[i];
}