前回までで、AlisのICOコントラクトのうちトークンの実装部分とクラウドセールの部分を見てきました。
今回は、ウォレット(マルチシグ)の実装を見ていきます。
マルチシグの詳しい説明は省略しますが、ウォレットの操作に複数の承認者が必要、というものです。
ベースとなるAlisFundはMultiSigWalletを継承しているだけです。
https://github.com/AlisProject/ico-contracts/blob/master/contracts/AlisFund.sol
contract AlisFund is MultiSigWallet {
function AlisFund(address[] _owners, uint _required)
public
validRequirement(_owners.length, _required)
MultiSigWallet(_owners, _required)
{
}
}
validRequirementはオーナーの数や、最低承認数をバリデーションしているところで、本体のウォレットがMultiSigWalletになります。
https://github.com/AlisProject/ico-contracts/blob/master/contracts/lib/MultiSigWallet.sol
isOwnerにオーナーのアドレスを入れることで、アドレスからオーナーを判定できるマッピングを作成し、引数で与えられたオーナーの配列と必要承認数を定義しています。
function MultiSigWallet(address[] _owners, uint _required)
public
validRequirement(_owners.length, _required)
{
for (uint i=0; i<_owners.length; i++) {
if (isOwner[_owners[i]] || _owners[i] == 0)
throw;
isOwner[_owners[i]] = true;
}
owners = _owners;
required = _required;
}
このマルチシグがどのように動いているかを見ていきます。ベースの単位はトランザクションになります。
ウォレットに対してトランザクションが定義されており、このトランザクションを実行していいかどうか、というところでマルチシグが利用されています。
struct Transaction {
address destination;
uint value;
bytes data;
bool executed;
}
このトランザクション一個一個にtransactionIdが割り当てられるようになっていて、transactionIdをキーにしたtransactionのマッピングと
mapping (uint => Transaction) public transactions;
各トランザクションを各オーナーが承認したことを記録するconfirmationsが用意されています。
mapping (uint => mapping (address => bool)) public confirmations;
ですので、各オーナーはconfirmationsを以下のように操作することでtransactionを承認します。
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);
}
また、一度承認したものでも取り消せるようになっています。
function revokeConfirmation(uint transactionId)
public
ownerExists(msg.sender)
confirmed(transactionId, msg.sender)
notExecuted(transactionId)
{
confirmations[transactionId][msg.sender] = false;
Revocation(msg.sender, transactionId);
}
そして、以下のisConfirmedによってトランザクション実行時に承認が得られているかを確認することができるようになります。
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;
}
}
マルチシグに関しては以上のような仕組みで実現していて、後の処理はトランザクションを実行する処理なので今回は割愛します。
一つ、ownersの配列からオーナーを削除する処理だけイーサリアムらしさがあるので紹介します。
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);
}
この処理は、ownersにあらかじめ登録されている中で特定のオーナーを削除する処理で、配列のi番目の要素を抜く処理を書いてあります。
通常のプログラムだと、i番目を抜いた後に、後続の要素をずらしていくと思いますが、データの更新にGasがかかるイーサリアムの場合には、配列の長さが想定できない場合にこの処理を書いてしまうと想定外にGasが使われてしまう可能性があります。
これを防ぐために上記の処理では、i番目に削除したい要素を見つけた場合、そこに一番後ろの要素を代入し、最後に一番後ろの要素を削除しています。
もし、一番後ろに削除したい要素があった場合には、forの中では要素が見つからずにただ最後の要素を削除するだけになります。
上記のように、なるべく書き込みコストをかけずに処理を行うような工夫がされています。
以上、今回はマルチシグについて見てきました。
次回はテスト周りを確認します。