Transaction Hash:
Block:
6620909 at Nov-01-2018 01:50:10 AM +UTC
Transaction Fee:
0.0001348824 ETH
$0.28
Gas Used:
56,201 Gas / 2.4 Gwei
Emitted Events:
| 119 |
AccountRegistryLogic.AccountCreated( accountId=33425, newUser=0x254bf3d6...7788f475c )
|
Account State Difference:
| Address | Before | After | State Difference | ||
|---|---|---|---|---|---|
| 0x8fF2B014...fB4ff893C |
1.466261647593325726 Eth
Nonce: 23566
|
1.466126765193325726 Eth
Nonce: 23567
| 0.0001348824 | ||
| 0xa08B00eB...86793Af55 | |||||
|
0xb2930B35...e543a0347
Miner
| (MiningPoolHub: Old Address) | 16,325.82073112415750582 Eth | 16,325.82086600655750582 Eth | 0.0001348824 |
Execution Trace
AccountRegistryLogic.createAccount( _newUser=0x254bf3d62f51dd08f44CFF55C1c1cc77788f475c )
-
AccountRegistry.createNewAccount( _newUser=0x254bf3d62f51dd08f44CFF55C1c1cc77788f475c ) -
AccountRegistry.accountIdForAddress( _address=0x254bf3d62f51dd08f44CFF55C1c1cc77788f475c ) => ( 33425 )
createAccount[AccountRegistryLogic (ln:287)]
createAccountForUser[AccountRegistryLogic (ln:288)]createNewAccount[AccountRegistryLogic (ln:302)]accountIdForAddress[AccountRegistryLogic (ln:303)]AccountCreated[AccountRegistryLogic (ln:304)]
File 1 of 2: AccountRegistryLogic
File 2 of 2: AccountRegistry
pragma solidity 0.4.24;
/**
* @title Ownable
* @dev The Ownable contract has an owner address, and provides basic authorization control
* functions, this simplifies the implementation of "user permissions".
*/
contract Ownable {
address public owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev The Ownable constructor sets the original `owner` of the contract to the sender
* account.
*/
function Ownable() public {
owner = msg.sender;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
/**
* @dev Allows the current owner to transfer control of the contract to a newOwner.
* @param newOwner The address to transfer ownership to.
*/
function transferOwnership(address newOwner) public onlyOwner {
require(newOwner != address(0));
emit OwnershipTransferred(owner, newOwner);
owner = newOwner;
}
}
contract SigningLogicInterface {
function recoverSigner(bytes32 _hash, bytes _sig) external pure returns (address);
function generateRequestAttestationSchemaHash(
address _subject,
address _attester,
address _requester,
bytes32 _dataHash,
uint256[] _typeIds,
bytes32 _nonce
) external view returns (bytes32);
function generateAttestForDelegationSchemaHash(
address _subject,
address _requester,
uint256 _reward,
bytes32 _paymentNonce,
bytes32 _dataHash,
uint256[] _typeIds,
bytes32 _requestNonce
) external view returns (bytes32);
function generateContestForDelegationSchemaHash(
address _requester,
uint256 _reward,
bytes32 _paymentNonce
) external view returns (bytes32);
function generateStakeForDelegationSchemaHash(
address _subject,
uint256 _value,
bytes32 _paymentNonce,
bytes32 _dataHash,
uint256[] _typeIds,
bytes32 _requestNonce,
uint256 _stakeDuration
) external view returns (bytes32);
function generateRevokeStakeForDelegationSchemaHash(
uint256 _subjectId,
uint256 _attestationId
) external view returns (bytes32);
function generateAddAddressSchemaHash(
address _senderAddress,
bytes32 _nonce
) external view returns (bytes32);
function generateVoteForDelegationSchemaHash(
uint16 _choice,
address _voter,
bytes32 _nonce,
address _poll
) external view returns (bytes32);
function generateReleaseTokensSchemaHash(
address _sender,
address _receiver,
uint256 _amount,
bytes32 _uuid
) external view returns (bytes32);
function generateLockupTokensDelegationSchemaHash(
address _sender,
uint256 _amount,
bytes32 _nonce
) external view returns (bytes32);
}
interface AccountRegistryInterface {
function accountIdForAddress(address _address) public view returns (uint256);
function addressBelongsToAccount(address _address) public view returns (bool);
function createNewAccount(address _newUser) external;
function addAddressToAccount(
address _newAddress,
address _sender
) external;
function removeAddressFromAccount(address _addressToRemove) external;
}
/**
* @title Bloom account registry
* @notice Account Registry Logic provides a public interface for Bloom and users to
* create and control their Bloom Ids.
* Users can associate create and accept invites and associate additional addresses with their BloomId.
* As the Bloom protocol matures, this contract can be upgraded to enable new capabilities
* without needing to migrate the underlying Account Registry storage contract.
*
* In order to invite someone, a user must generate a new public key private key pair
* and sign their own ethereum address. The user provides this signature to the
* `createInvite` function where the public key is recovered and the invite is created.
* The inviter should then share the one-time-use private key out of band with the recipient.
* The recipient accepts the invite by signing their own address and passing that signature
* to the `acceptInvite` function. The contract should recover the same public key, demonstrating
* that the recipient knows the secret and is likely the person intended to receive the invite.
*
* @dev This invite model is supposed to aid usability by not requiring the inviting user to know
* the Ethereum address of the recipient. If the one-time-use private key is leaked then anyone
* else can accept the invite. This is an intentional tradeoff of this invite system. A well built
* dApp should generate the private key on the backend and sign the user's address for them. Likewise,
* the signing should also happen on the backend (not visible to the user) for signing an address to
* accept an invite. This reduces the private key exposure so that the dApp can still require traditional
* checks like verifying an associated email address before finally signing the user's Ethereum address.
*
* @dev The private key generated for this invite system should NEVER be used for an Ethereum address.
* The private key should be used only for the invite flow and then it should effectively be discarded.
*
* @dev If a user DOES know the address of the person they are inviting then they can still use this
* invite system. All they have to do then is sign the address of the user being invited and share the
* signature with them.
*/
contract AccountRegistryLogic is Ownable{
SigningLogicInterface public signingLogic;
AccountRegistryInterface public registry;
address public registryAdmin;
/**
* @notice The AccountRegistry constructor configures the signing logic implementation
* and creates an account for the user who deployed the contract.
* @dev The owner is also set as the original registryAdmin, who has the privilege to
* create accounts outside of the normal invitation flow.
* @param _signingLogic The address of the deployed SigningLogic contract
* @param _registry The address of the deployed account registry
*/
constructor(
SigningLogicInterface _signingLogic,
AccountRegistryInterface _registry
) public {
signingLogic = _signingLogic;
registry = _registry;
registryAdmin = owner;
}
event AccountCreated(uint256 indexed accountId, address indexed newUser);
event InviteCreated(address indexed inviter, address indexed inviteAddress);
event InviteAccepted(address recipient, address indexed inviteAddress);
event AddressAdded(uint256 indexed accountId, address indexed newAddress);
event AddressRemoved(uint256 indexed accountId, address indexed oldAddress);
event RegistryAdminChanged(address oldRegistryAdmin, address newRegistryAdmin);
event SigningLogicChanged(address oldSigningLogic, address newSigningLogic);
event AccountRegistryChanged(address oldRegistry, address newRegistry);
/**
* @dev Addresses with Bloom accounts already are not allowed
*/
modifier onlyNonUser {
require(!registry.addressBelongsToAccount(msg.sender));
_;
}
/**
* @dev Addresses without Bloom accounts already are not allowed
*/
modifier onlyUser {
require(registry.addressBelongsToAccount(msg.sender));
_;
}
/**
* @dev Zero address not allowed
*/
modifier nonZero(address _address) {
require(_address != 0);
_;
}
/**
* @dev Restricted to registryAdmin
*/
modifier onlyRegistryAdmin {
require(msg.sender == registryAdmin);
_;
}
// Signatures contain a nonce to make them unique. usedSignatures tracks which signatures
// have been used so they can't be replayed
mapping (bytes32 => bool) public usedSignatures;
// Mapping of public keys as Ethereum addresses to invite information
// NOTE: the address keys here are NOT Ethereum addresses, we just happen
// to work with the public keys in terms of Ethereum address strings because
// this is what `ecrecover` produces when working with signed text.
mapping(address => bool) public pendingInvites;
/**
* @notice Change the implementation of the SigningLogic contract by setting a new address
* @dev Restricted to AccountRegistry owner and new implementation address cannot be 0x0
* @param _newSigningLogic Address of new SigningLogic implementation
*/
function setSigningLogic(SigningLogicInterface _newSigningLogic) public nonZero(_newSigningLogic) onlyOwner {
address oldSigningLogic = signingLogic;
signingLogic = _newSigningLogic;
emit SigningLogicChanged(oldSigningLogic, signingLogic);
}
/**
* @notice Change the address of the registryAdmin, who has the privilege to create new accounts
* @dev Restricted to AccountRegistry owner and new admin address cannot be 0x0
* @param _newRegistryAdmin Address of new registryAdmin
*/
function setRegistryAdmin(address _newRegistryAdmin) public onlyOwner nonZero(_newRegistryAdmin) {
address _oldRegistryAdmin = registryAdmin;
registryAdmin = _newRegistryAdmin;
emit RegistryAdminChanged(_oldRegistryAdmin, registryAdmin);
}
/**
* @notice Change the address of AccountRegistry, which enables authorization of subject comments
* @dev Restricted to owner and new address cannot be 0x0
* @param _newRegistry Address of new Account Registry contract
*/
function setAccountRegistry(AccountRegistryInterface _newRegistry) public nonZero(_newRegistry) onlyOwner {
address oldRegistry = registry;
registry = _newRegistry;
emit AccountRegistryChanged(oldRegistry, registry);
}
/**
* @notice Create an invite using the signing model described in the contract description
* @dev Recovers public key of invitation key pair using
* @param _sig Signature of one-time-use keypair generated for invite
*/
function createInvite(bytes _sig) public onlyUser {
address inviteAddress = signingLogic.recoverSigner(keccak256(abi.encodePacked(msg.sender)), _sig);
require(!pendingInvites[inviteAddress]);
pendingInvites[inviteAddress] = true;
emit InviteCreated(msg.sender, inviteAddress);
}
/**
* @notice Accept an invite using the signing model described in the contract description
* @dev Recovers public key of invitation key pair
* Assumes signed message matches format described in recoverSigner
* Restricted to addresses that are not already registered by a user
* Invite is accepted by setting recipient to nonzero address for invite associated with recovered public key
* and creating an account for the sender
* @param _sig Signature for `msg.sender` via the same key that issued the initial invite
*/
function acceptInvite(bytes _sig) public onlyNonUser {
address inviteAddress = signingLogic.recoverSigner(keccak256(abi.encodePacked(msg.sender)), _sig);
require(pendingInvites[inviteAddress]);
pendingInvites[inviteAddress] = false;
createAccountForUser(msg.sender);
emit InviteAccepted(msg.sender, inviteAddress);
}
/**
* @notice Create an account instantly without an invitation
* @dev Restricted to the "invite admin" which is managed by the Bloom team
* @param _newUser Address of the user receiving an account
*/
function createAccount(address _newUser) public onlyRegistryAdmin {
createAccountForUser(_newUser);
}
/**
* @notice Create an account for a user and emit an event
* @dev Records address as taken so it cannot be used to sign up for another account
* accountId is a unique ID across all users generated by calculating the length of the accounts array
* addressId is the position in the unordered list of addresses associated with a user account
* AccountInfo is a struct containing accountId and addressId so all addresses can be found for a user
* new Login structs represent user accounts. The first one is pushed onto the array associated with a user's accountID
* To push a new account onto the same Id, accounts array should be addressed accounts[_accountID - 1].push
* @param _newUser Address of the new user
*/
function createAccountForUser(address _newUser) internal nonZero(_newUser) {
registry.createNewAccount(_newUser);
uint256 _accountId = registry.accountIdForAddress(_newUser);
emit AccountCreated(_accountId, _newUser);
}
/**
* @notice Add an address to an existing id on behalf of a user to pay the gas costs
* @param _newAddress Address to add to account
* @param _newAddressSig Signed message from new address confirming ownership by the sender
* @param _senderSig Signed message from address currently associated with account confirming intention
* @param _sender User requesting this action
* @param _nonce uuid used when generating sigs to make them one time use
*/
function addAddressToAccountFor(
address _newAddress,
bytes _newAddressSig,
bytes _senderSig,
address _sender,
bytes32 _nonce
) public onlyRegistryAdmin {
addAddressToAccountForUser(_newAddress, _newAddressSig, _senderSig, _sender, _nonce);
}
/**
* @notice Add an address to an existing id by a user
* @dev Wrapper for addAddressTooAccountForUser with msg.sender as sender
* @param _newAddress Address to add to account
* @param _newAddressSig Signed message from new address confirming ownership by the sender
* @param _senderSig Signed message from msg.sender confirming intention by the sender
* @param _nonce uuid used when generating sigs to make them one time use
*/
function addAddressToAccount(
address _newAddress,
bytes _newAddressSig,
bytes _senderSig,
bytes32 _nonce
) public onlyUser {
addAddressToAccountForUser(_newAddress, _newAddressSig, _senderSig, msg.sender, _nonce);
}
/**
* @notice Add an address to an existing id
* @dev Checks that new address signed _sig
* @param _newAddress Address to add to account
* @param _newAddressSig Signed message from new address confirming ownership by the sender
* @param _senderSig Signed message from new address confirming ownership by the sender
* @param _sender User requesting this action
* @param _nonce uuid used when generating sigs to make them one time use
*/
function addAddressToAccountForUser(
address _newAddress,
bytes _newAddressSig,
bytes _senderSig,
address _sender,
bytes32 _nonce
) private nonZero(_newAddress) {
require(!usedSignatures[keccak256(abi.encodePacked(_newAddressSig))], "Signature not unique");
require(!usedSignatures[keccak256(abi.encodePacked(_senderSig))], "Signature not unique");
usedSignatures[keccak256(abi.encodePacked(_newAddressSig))] = true;
usedSignatures[keccak256(abi.encodePacked(_senderSig))] = true;
// Confirm new address is signed by current address
bytes32 _currentAddressDigest = signingLogic.generateAddAddressSchemaHash(_newAddress, _nonce);
require(_sender == signingLogic.recoverSigner(_currentAddressDigest, _senderSig));
// Confirm current address is signed by new address
bytes32 _newAddressDigest = signingLogic.generateAddAddressSchemaHash(_sender, _nonce);
require(_newAddress == signingLogic.recoverSigner(_newAddressDigest, _newAddressSig));
registry.addAddressToAccount(_newAddress, _sender);
uint256 _accountId = registry.accountIdForAddress(_newAddress);
emit AddressAdded(_accountId, _newAddress);
}
/**
* @notice Remove an address from an account for a user
* @dev Restricted to admin
* @param _addressToRemove Address to remove from account
*/
function removeAddressFromAccountFor(
address _addressToRemove
) public onlyRegistryAdmin {
uint256 _accountId = registry.accountIdForAddress(_addressToRemove);
registry.removeAddressFromAccount(_addressToRemove);
emit AddressRemoved(_accountId, _addressToRemove);
}
}File 2 of 2: AccountRegistry
pragma solidity 0.4.24;
/**
* @title Ownable
* @dev The Ownable contract has an owner address, and provides basic authorization control
* functions, this simplifies the implementation of "user permissions".
*/
contract Ownable {
address public owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev The Ownable constructor sets the original `owner` of the contract to the sender
* account.
*/
function Ownable() public {
owner = msg.sender;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
/**
* @dev Allows the current owner to transfer control of the contract to a newOwner.
* @param newOwner The address to transfer ownership to.
*/
function transferOwnership(address newOwner) public onlyOwner {
require(newOwner != address(0));
emit OwnershipTransferred(owner, newOwner);
owner = newOwner;
}
}
interface AccountRegistryInterface {
function accountIdForAddress(address _address) public view returns (uint256);
function addressBelongsToAccount(address _address) public view returns (bool);
function createNewAccount(address _newUser) external;
function addAddressToAccount(
address _newAddress,
address _sender
) external;
function removeAddressFromAccount(address _addressToRemove) external;
}
/**
* @title Bloom account registry
* @notice Account Registry implements the Bloom ID data structures
* and the low-level account administration functions.
* The account administration functions are not publicly accessible.
* Account Registry Logic implements the public functions which access the functions in Account Registry.
*/
contract AccountRegistry is Ownable, AccountRegistryInterface{
address public accountRegistryLogic;
/**
* @notice The AccountRegistry constructor configures the account registry logic implementation
* and creates an account for the user who deployed the contract.
* @dev The owner is also set as the original registryAdmin, who has the privilege to
* create accounts outside of the normal invitation flow.
* @param _accountRegistryLogic Address of deployed Account Registry Logic implementation
*/
constructor(
address _accountRegistryLogic
) public {
accountRegistryLogic = _accountRegistryLogic;
}
event AccountRegistryLogicChanged(address oldRegistryLogic, address newRegistryLogic);
/**
* @dev Zero address not allowed
*/
modifier nonZero(address _address) {
require(_address != 0);
_;
}
modifier onlyAccountRegistryLogic() {
require(msg.sender == accountRegistryLogic);
_;
}
// Counter to generate unique account Ids
uint256 numAccounts;
mapping(address => uint256) public accountByAddress;
/**
* @notice Change the address of the registry logic which has exclusive write control over this contract
* @dev Restricted to AccountRegistry owner and new admin address cannot be 0x0
* @param _newRegistryLogic Address of new registry logic implementation
*/
function setRegistryLogic(address _newRegistryLogic) public onlyOwner nonZero(_newRegistryLogic) {
address _oldRegistryLogic = accountRegistryLogic;
accountRegistryLogic = _newRegistryLogic;
emit AccountRegistryLogicChanged(_oldRegistryLogic, accountRegistryLogic);
}
/**
* @notice Retreive account ID associated with a user's address
* @param _address Address to look up
* @return account id as uint256 if exists, otherwise reverts
*/
function accountIdForAddress(address _address) public view returns (uint256) {
require(addressBelongsToAccount(_address));
return accountByAddress[_address];
}
/**
* @notice Check if an address is associated with any user account
* @dev Check if address is associated with any user by cross validating
* the accountByAddress with addressByAccount
* @param _address Address to check
* @return true if address has been assigned to user. otherwise reverts
*/
function addressBelongsToAccount(address _address) public view returns (bool) {
return accountByAddress[_address] > 0;
}
/**
* @notice Create an account for a user and emit an event
* @param _newUser Address of the new user
*/
function createNewAccount(address _newUser) external onlyAccountRegistryLogic nonZero(_newUser) {
require(!addressBelongsToAccount(_newUser));
numAccounts++;
accountByAddress[_newUser] = numAccounts;
}
/**
* @notice Add an address to an existing id
* @param _newAddress Address to add to account
* @param _sender User requesting this action
*/
function addAddressToAccount(
address _newAddress,
address _sender
) external onlyAccountRegistryLogic nonZero(_newAddress) {
// check if address belongs to someone else
require(!addressBelongsToAccount(_newAddress));
accountByAddress[_newAddress] = accountIdForAddress(_sender);
}
/**
* @notice Remove an address from an id
* @param _addressToRemove Address to remove from account
*/
function removeAddressFromAccount(
address _addressToRemove
) external onlyAccountRegistryLogic {
delete accountByAddress[_addressToRemove];
}
}