These contracts are documented on Bloom's website here: https://bloom.co/docs
The documentation is also available in the readme below.
Accounts
Anyone can use the Bloom Protocol smart contracts using any address. Bloom supports regular EOA addresses or smart contracts like multisigs. The AccountRegistryLogic contract enables users to demonstrate ownership of multiple addresses by linking the addresses on chain.
Signing Logic
Bloom relies on the signTypedData proposal described in EIP712 for many protocol interactions including allowing users to delegate transactions to Bloom to pay transaction costs. The SigningLogic contract contains all of the logic to recover addresses from signTypedData signatures.
Attestations
One of the core components of Bloom protocol is Attestations. The attestation contract enables users to build a collection of verified information they can use to sign up for third party services.
Accreditation
Bloom maintains a whitelist of accredited data verifiers via an Accreditation Repo. Currently Bloom retains the rights to grant and revoke accreditation. In the future this contract can be upgraded to enable community voting based accreditation.
Token Escrow
Users can lock up BLT in the Token Escrow contract. Once locked up users can send micropayments to other users by signing payment authorizations. Payments are redeemed by completing attestations.
Voting
Bloom’s protocol heavily relies on community voting to make important protocol decisions. Anyone can create a Poll in the Bloom network by interacting with the VotingCenter contract.
- Clone the repository
- Run
yarn install
to install dependencies - Copy
truffle.js.example
totruffle.js
- Compile contract typings
./bin/compile-typings
- Run tests with
./bin/test
If you plan to use a truffleLedgerProvider, also do yarn add @ledgerhq/[email protected]
It was removed from package.json due to an issue with CI's test machines
The tests are written in Typescript. If you make changes to the contracts which impact the ABI, update the typings with:
./bin/compile-typings --reset
The tests are in the ts_test
folder. They are separated by contract. Each test follows the same basic structure.
- Load the contract and the relevant interfaces
- Import the test-rpc accounts
- Initialize the accounts with properties relevant to the contract (create accounts, gift them tokens, etc)
- Test each contract function
File | Dependencies | Description |
---|---|---|
AccreditationRepo.sol | open-zeppelin#Ownable | Owner can update interface contracts |
AirdropProxy.sol | open-zeppelin#Ownable | Owner can update interface contracts |
AirdropProxy.sol | open-zeppelin#Pausable | This contract holds collateralized tokens so it should be able to be paused if a vulnerability is discovered |
AirdropProxy.sol | open-zeppelin#SafeERC20 | Wrapper for ERC20 methods that throw on failure |
AirdropProxy.sol | open-zeppelin#ERC20 | ERC20 methods to enable this contract to interact with BLT |
AirdropProxy.sol | open-zeppelin#HasNoEther | Enables owner to retrieve eth from this contract and attempts to stop eth from being deposited |
SigningLogic.sol | open-zeppelin#ECRecovery | ECRecovery.recover is used for all delegated transactions and other interactions requiring a signature from the user |
TokenEscrowMarketplace.sol | open-zeppelin#SafeERC20 | Wrapper for ERC20 methods that throw on failure |
TokenEscrowMarketplace.sol | open-zeppelin#ERC20 | ERC20 methods to enable this contract to interact with BLT |
TokenEscrowMarketplace.sol | open-zeppelin#SafeMath | Safety checks for math operations to manipulate escrow balances |
Previously Bloom's smart contracts used the separate storage & logic contract design pattern. This enabled Bloom to swap out contract logic to fix bugs or add features. As the protocol has matured this centralized control is no longer appropriate. Contract storage and logic is now immutable upon deployment and initialization. Upgrades can only be made by deploying new contracts and recommending the community transition to the new contracts.
Contracts inherit Initializable.sol to allow a specified address to migrate data without signature validation. This is only allowed during initialization of the contract and the privilege is removed once migration is complete.
Users can delegate certain protocol actions have a third party pay the transaction fees. Many functions throughout these contracts are implemented with 3 functions.
- Action - Public function enabling users to interact with the contract directly
- ActionFor - Delegated function enabling 3rd party to call Action on behalf of a user
- ActionForUser - Private function enabling the logic of this action. Both Action and ActionFor call ActionForUser.
Users authorize a 3rd party to perform actions on their behalf by signing a message containing their intended protocol action with a private key. Each signature contains a nonce to make the signature single-use. The contracts maintain a mapping of used signatures.
mapping (bytes32 => bool) public usedSignatures;
If a user signs a delegated transaction, the 3rd party is only able to submit that signature one time. If the user needs to complete the same action again, they sign a new delegated transaction with a new nonce.
The signature definition is contained in the Signing Logic contract. This contract is inherited by all contracts needing delegated actions.
Account Registry Logic implements the storage and logic to enable users to link multiple addresses to the same owner.
Links are stored as mapping from addresses to a link Id. Users can have any number of addresses point to the same linkId.
mapping(address => uint256) public linkIds;
Public getter to check if an address is linked to another address. Returns 0 if no link.
function linkIds(address _address) public view returns (uint256)
// Web3
const addressA = '0xabc123...'
const addressB = '0xdef456...'
accountRegistry = AccountRegistryLogic.at("0x123...")
const linkA = accountRegistry.linkIds.call(addressA)
const linkB = accountRegistry.linkIds.call(addressB)
if (linkA === linkB && linkA !== 0) {
// Addresses are linked
}
Store a link demonstrating ownership of multiple addresses. Each address must sign a message indicating intention to be linked. currentAddress
may be linked to other addresses. newAddress
must be unclaimed.
A user can submit this transaction from their own address or it can be submitted by a third party on their behalf.
/**
* @notice Add an address to an existing id by a user
* @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 intention by the sender
* @param _nonce Random hex string used when generating sigs to make them one time use
*/
function linkAddresses(
address _currentAddress,
bytes _currentAddressSig,
address _newAddress,
bytes _newAddressSig,
bytes32 _nonce
) public;
const getFormattedTypedDataAddAddress = (
contractAddress: string,
chainId: number,
addressToAdd: string,
nonce: string,
): IFormattedTypedData => {
return {
types: {
EIP712Domain: [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' },
],
AddAddress: [
{ name: 'addressToAdd', type: 'address'},
{ name: 'nonce', type: 'bytes32'},
]
},
primaryType: 'AddAddress',
domain: {
name: 'Bloom Account Registry',
version: '2',
chainId: chainId,
verifyingContract: contractAddress,
},
message: {
addressToAdd: addressToAdd,
nonce: nonce
}
}
}
//Web3 pseudocode
registryLogic = AccountRegistryLogic.at("0x123...")
nonce = sha3(uuid())
newAddressLinkSig = ethSigUtil.signTypedData(unclaimedPrivkey, {
data: getFormattedTypedDataAddAddress(registryLogicAddress, 1, alice, nonce)
})
currentAddressLinkSig = ethSigUtil.signTypedData(alicePrivkey, {
data: getFormattedTypedDataAddAddress(registryLogicAddress, 1, unclaimed, nonce)
})
registryLogic.linkAddresses(alice, currentAddressLinkSig, unclaimed, newAddressLinkSig, nonce, {from: anyone})
Remove an address from a link relationship to another address. An address can only be unlinked if authorized by the a message signed by the associated private key. A user can submit this transaction from their own address or it can be submitted by a third party on their behalf.
function unlinkAddress(
address _addressToRemove,
bytes32 _nonce,
bytes _unlinkSignature
) public;
const getFormattedTypedDataRemoveAddress = (
contractAddress: string,
chainId: number,
addressToRemove: string,
nonce: string,
): IFormattedTypedData => {
return {
types: {
EIP712Domain: [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' },
],
RemoveAddress: [
{ name: 'addressToRemove', type: 'address'},
{ name: 'nonce', type: 'bytes32'},
]
},
primaryType: 'RemoveAddress',
domain: {
name: 'Bloom Account Registry',
version: '2',
chainId: chainId,
verifyingContract: contractAddress,
},
message: {
addressToRemove: addressToRemove,
nonce: nonce
}
}
}
//Web3 pseudocode to unlink a linked address
registryLogic = AccountRegistryLogic.at("0x123...")
nonce = sha3(uuid())
await registryLogic.linkAddresses(alice, currentAddressLinkSig, unclaimed, newAddressLinkSig, nonceHash, { from: bob })
const unlinkSig = ethSigUtil.signTypedData(unclaimedPrivkey, {
data: getFormattedTypedDataRemoveAddress(registryLogicAddress, 1, unclaimed, nonceHash)
})
await registryLogic.unlinkAddress(
unclaimed,
nonceHash,
unlinkSig,
{ from: bob }
)
Bloom relies on the signTypedData
proposal described in EIP712 for many protocol interactions including allowing users to delegate transactions to Bloom to pay transaction costs. SigningLogic implements all of the EIP712 schemas for the Bloom Protocol contracts. It also implements utility functions to manage signatures and recover signers. SigningLogic is inherited by the other Bloom Protocol contracts.
Recover Signer returns the address of the user who signed a message.
function recoverSigner(bytes32 _hash, bytes _sig) external pure returns (address)
Each signature contains a domain specification so the user understands how their signature will be used. The domain specifies the dApp name, version, chain Id and the contract intended to use the signatures.
bytes32 constant EIP712DOMAIN_TYPEHASH = keccak256(
"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
);
bytes32 DOMAIN_SEPARATOR = hash(EIP712Domain({
name: "Bloom",
version: '1',
}));
struct EIP712Domain {
string name;
string version;
uint256 chainId;
address verifyingContract;
}
function hash(EIP712Domain eip712Domain) internal pure returns (bytes32) {
return keccak256(abi.encode(
EIP712DOMAIN_TYPEHASH,
keccak256(bytes(eip712Domain.name)),
keccak256(bytes(eip712Domain.version)),
eip712Domain.chainId,
eip712Domain.verifyingContract
));
}
constructor (string name, string version, uint256 chainId) public {
DOMAIN_SEPARATOR = hash(EIP712Domain({
name: name,
version: version,
chainId: chainId,
verifyingContract: this
}));
}
Each signature containing an authorization for a delegated Bloom Protocol action is single use. The schema contains a nonce to make each signature unique even if the intended actionis the same. SigningLogic maintains a list of signature digests that have been used and reverts if a signature is submitted more than once.
mapping (bytes32 => bool) public usedSignatures;
function burnSignatureDigest(bytes32 _signatureDigest, address _sender) internal {
bytes32 _txDataHash = keccak256(abi.encode(_signatureDigest, _sender));
require(!usedSignatures[_txDataHash], "Signature not unique");
usedSignatures[_txDataHash] = true;
}
In order to recover the signer of a signTypedData
signature, the contract must know the message that was signed. SigningLogic provides functions to generate the digest for every signTypedData
action in the Bloom dApp.
struct AddAddress {
address sender;
bytes32 nonce;
}
bytes32 constant ADD_ADDRESS_TYPEHASH = keccak256(
"AddAddress(address sender,bytes32 nonce)"
);
function hash(AddAddress request) internal pure returns (bytes32) {
return keccak256(abi.encode(
ADD_ADDRESS_TYPEHASH,
request.sender,
request.nonce
));
}
function generateAddAddressSchemaHash(
address _senderAddress,
bytes32 _nonce
) external view returns (bytes32) {
return keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR,
hash(AddAddress(
_senderAddress,
_nonce
))
)
);
}
const getFormattedTypedDataAddAddress = (
contractAddress: string,
chainId: number,
addressToAdd: string,
nonce: string,
): IFormattedTypedData => {
return {
types: {
EIP712Domain: [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' },
],
AddAddress: [
{ name: 'addressToAdd', type: 'address'},
{ name: 'nonce', type: 'bytes32'},
]
},
primaryType: 'AddAddress',
domain: {
name: 'Bloom Account Registry',
version: '2',
chainId: chainId,
verifyingContract: contractAddress,
},
message: {
addressToAdd: addressToAdd,
nonce: nonce
}
}
}
//Web3 pseudocode
registryLogic = AccountRegistryLogic.at("0x123...")
nonce = sha3(uuid())
newAddressLinkSig = ethSigUtil.signTypedData(unclaimedPrivkey, {
data: getFormattedTypedDataAddAddress(registryLogicAddress, 1, alice, nonce)
})
currentAddressLinkSig = ethSigUtil.signTypedData(alicePrivkey, {
data: getFormattedTypedDataAddAddress(registryLogicAddress, 1, unclaimed, nonce)
})
AttestationLogic allows attesters to receive payment for completing authorizations when requested by a third party and only when authorized by the subject. Requesters, attesters and subjects coordinate off-chain to verify identity information. Attesters publish their decision on chain by either 'attesting' the information or 'contesting' it. Either way they receive payment from the requester's escrow account. Users can also revoke attestations through the AttestationLogic contract by emitting a revoke event referencing the revocation links contained in the attestation data tree.
Event based storage is preferable to EVM state storage for variables not critical to the contract logic in order to save a significant amount of gas. Events are just as permanent as contract storage, they just cannot be referenced from within a transaction. Attestation data such as dataHash and requesterId are not critical to the logic of the contract. However they are critical to the Bloom Protocol. They are stored by emitting an event at the time of an attestation.
event TraitAttested(
address subject,
address attester,
address requester,
bytes32 dataHash
);
Attestations may reference a single type of identity data or a group of data. The dataHash stores the root of the attestation data Merkle tree. The plaintext data itself is never stored on the blockchain. See the attestations-lib for more information on how the dataHash is formed.
If a 3rd party requests a Bloom user to reveal the data that was submitted as part of an attestation, the user can choose to reveal none, some or all of the data by sending the 3rd party the merkle proof and selected plaintext data.
The entity requesting the attestation may be different from the user who is the subject of the attestation. Some 3rd party may be interested in a user having their name, phone number and address verified prior to offering them some service.
Attestation Logic provides a public interface for Bloom and users to submit attestations, receive payment and revoke previous attestations.
The attester calls attest to submit an attestation and receive payment from the requester. A simplified view of the attestation process is:
1. Requester is interested in having an attestation performed for a subject (Bloom user)
2. Requester finds an attester willing to do the work for a set reward in BLT
3. Requester obtains a signature from the subject authorizing the attestation
4. Requester signs a signature authorizing the release of BLT to the attester upon completion of the attestation job.
5. Attester attempts to verify data.
6. If successful, attester writes attestation to the contract and receives payment. If unsuccesful, attester redeems payment signature for the full reward but does not write to the contract. (see contest)
/**
* @notice Function for attester to submit attestation from their own account)
* @dev Wrapper for attestForUser using msg.sender
* @param _subject User this attestation is about
* @param _requester User requesting and paying for this attestation in BLT
* @param _reward Payment to attester from requester in BLT
* @param _paymentNonce Nonce referenced in TokenEscrowMarketplace so payment sig can't be replayed
* @param _requesterSig Signature authorizing payment from requester to attester
* @param _dataHash Hash of data being attested and nonce
* @param _requestNonce Nonce in sig signed by subject so it can't be replayed
* @param _subjectSig Signed authorization from subject with attestation agreement
*/
function attest(
address _subject,
address _requester,
uint256 _reward,
bytes32 _paymentNonce,
bytes _requesterSig,
bytes32 _dataHash,
bytes32 _requestNonce,
bytes _subjectSig
) public;
const attestationLogic = AttestationLogic.at("0x132...")
const subjectSig = ethSigUtil.signTypedData(alicePrivkey, {
data: getFormattedTypedDataAttestationRequest(attestationLogicAddress, 1, combinedDataHash, nonce)
})
const tokenPaymentSig = ethSigUtil.signTypedData(davidPrivkey, {
data: getFormattedTypedDataPayTokens(
tokenEscrowMarketplaceAddress,
1,
david,
bob,
new BigNumber(1e17).toString(10),
nonce
)
})
await attestationLogic.attest(alice, david, new BigNumber(1e17), paymentNonce, tokenPaymentSig, dataHash, requestNonce, subjectSig, {
from: bob)
An attestation can be revoked by submitting a revokeAttestation
transaction containing the revocationLink string embeeded in the attestation data tree.
/**
* @notice Revoke an attestation
* @dev Link is included in dataHash and cannot be directly connected to a BloomID
* @param _link bytes string embedded in dataHash to link revocation
*/
function revokeAttestation(
bytes32 _link
) public;
const revokeLink = "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6"
await attestationLogic.revokeAttestation(revokeLink, {
from: bob
})
The attester calls contest to redeem payment for a failed attestation without leaving a permanent negative record associated with a user's BloomID.
/**
* @notice Function for attester to reject an attestation and receive payment
* without associating the negative attestation with the subject
* @param _requester User requesting and paying for this attestation in BLT
* @param _reward Payment to attester from requester in BLT
* @param _paymentNonce Nonce referenced in TokenEscrowMarketplace so payment sig can't be replayed
* @param _requesterSig Signature authorizing payment from requester to attester
*/
function contest(
address _requester,
uint256 _reward,
bytes32 _paymentNonce,
bytes _requesterSig
) public;
const attestationLogic = AttestationLogic.at("0x132...")
const subjectSig = ethSigUtil.signTypedData(alicePrivkey, {
data: getFormattedTypedDataAttestationRequest(attestationLogicAddress, 1, combinedDataHash, nonce)
})
const tokenPaymentSig = ethSigUtil.signTypedData(davidPrivkey, {
data: getFormattedTypedDataPayTokens(
tokenEscrowMarketplaceAddress,
1,
david,
bob,
new BigNumber(1e17).toString(10),
paymentNonce
)
})
await attestationLogic.contest(
david,
new BigNumber(1e17),
paymentNonce,
tokenPaymentSig,
{from: attesterAddress}
)
Anyone can collect valid attestation signatures and submit the transaction on behalf of the attester in order to pay transaction fees.
function attestFor(
address _subject,
address _attester,
address _requester,
uint256 _reward,
bytes32 _paymentNonce,
bytes _requesterSig,
bytes32 _dataHash,
bytes32 _requestNonce,
bytes _subjectSig, // Sig of subject with dataHash and requestNonce
bytes _delegationSig
) public;
bytes32 constant ATTEST_FOR_TYPEHASH = keccak256(
"AttestFor(address subject,address requester,uint256 reward,bytes32 paymentNonce,bytes32 dataHash,bytes32 requestNonce)"
);
struct AttestFor {
address subject;
address requester;
uint256 reward;
bytes32 paymentNonce;
bytes32 dataHash;
bytes32 requestNonce;
}
function hash(AttestFor request) internal pure returns (bytes32) {
return keccak256(abi.encode(
ATTEST_FOR_TYPEHASH,
request.subject,
request.requester,
request.reward,
request.paymentNonce,
request.dataHash,
request.requestNonce
));
}
function generateAttestForDelegationSchemaHash(
address _subject,
address _requester,
uint256 _reward,
bytes32 _paymentNonce,
bytes32 _dataHash,
bytes32 _requestNonce
) external view returns (bytes32) {
return keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR,
hash(AttestFor(
_subject,
_requester,
_reward,
_paymentNonce,
_dataHash,
_requestNonce
))
)
);
}
function contestFor(
address _attester,
address _requester,
uint256 _reward,
bytes32 _paymentNonce,
bytes _requesterSig,
bytes _delegationSig
) public;
bytes32 constant CONTEST_FOR_TYPEHASH = keccak256(
"ContestFor(address requester,uint256 reward,bytes32 paymentNonce)"
);
struct ContestFor {
address requester;
uint256 reward;
bytes32 paymentNonce;
}
function hash(ContestFor request) internal pure returns (bytes32) {
return keccak256(abi.encode(
CONTEST_FOR_TYPEHASH,
request.requester,
request.reward,
request.paymentNonce
));
}
function generateContestForDelegationSchemaHash(
address _requester,
uint256 _reward,
bytes32 _paymentNonce
) external view returns (bytes32) {
return keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR,
hash(ContestFor(
_requester,
_reward,
_paymentNonce
))
)
);
}
The deployer can specify an initializer
address which has the privileges to write attestations to this contract without signature authorization during a data migration period. This privilege is revoked once initialization ends.
/**
* @notice Submit attestation completed prior to deployment of this contract
* @dev Gives initializer privileges to write attestations during the initialization period without signatures
* @param _requester user requesting this attestation be completed
* @param _attester user completing the attestation
* @param _subject user this attestation is about
* @param _dataHash hash of data being attested
*/
function migrateAttestation(
address _requester,
address _attester,
address _subject,
bytes32 _dataHash
) public onlyDuringInitialization;
TokenEscrowMarketplace is an ERC20 escrow contract that enables users to send BLT by exchanging signatures off-chain. Users approve the contract address to transfer BLT on their behalf using the standard ERC20.approve
function. After approval, either the user or the contract admin initiates the transfer of BLT into the contract. BLT balances are stored in the contract by address. Users should store BLT with the same address they will use to request and pay for attestations.
Once in the contract, users can send payments via signed message to another user. The signature transfers BLT from lockup to the recipient's wallet. Users can withdraw unused funds at any time.
Only the Attestation Logic contract is authorized to release funds to a third party using a payment signature upon completion of a job.
Each user has their spendable token balance stored within the marketplace contract, tokenEscrow
. Users can retrieve these tokens at any point by calling releaseTokensFromEscrow
or signing the intention to withdraw and having a third party submit the transaction on their behalf.
mapping(address => uint256) public tokenEscrow.
Token Escrow Marketplace provides a public interface for users to manage the BLT they pay or receive to participate in the Identity Attestation process. Users may deposit and withdraw tokens. Bloom may also perform these actions on a user’s behalf in order to pay the transaction costs. All actions require a signature from the user authorizing the action.
Move tokens from the user to tokenEscrow
. Must be preceeded by an ERC20 Approve function authorizing the transfer of tokens into the contract.
function moveTokensToEscrowLockup(uint256 _amount) public
const tokenEscrowMarketplace = TokenEscrowMarketplace.at([Marketplace contract address])
token.approve(tokenEscrowMarketplace.address, new BigNumber("100e18"));
tokenEscrowMarketplace.moveTokensToEscrowLockup(new BigNumber("100e18"))
Transfer tokens back to user.
function releaseTokensFromEscrow(uint256 _amount) public
const tokenEscrowMarketplace = TokenEscrowMarketplace.at([Marketplace contract address])
tokenEscrowMarketplace.releaseTokensFromEscrow(new BigNumber("10e18"))
A Bloom user who has locked up tokens in tokenEscrow
may send a signed payment authorization signature to another Bloom user. This signatures is submitted to Attestation Logic upon completion of an attestation job. If the attestation is successfully written to Attestation Repo, the tokens are released from tokenEscrow
and transfered to the recipient. Only the specified Attestation Logic contract can transfer locked up tokens from tokenEscrow
to a recipient.
See the attest
example to see how the payment signature is formed and submitted.
Users can delegate certain Token Escrow Marketplace actions to the Bloom admin in order to avoid paying transaction costs. The Bloom admin can also configure the external contracts associated with the marketplace contract.
Deposit tokens on behalf of a user. This action requires a specific one-time-use signature from a user authorizing the tokens to be locked up.
/**
* @notice Lockup tokens for set time period on behalf of user. Must be preceded by deposit into contract
* @param _sender User locking up their tokens
* @param _amount Tokens to lock up
* @param _nonce Random hex string used when generating sigs to make them one time use
* @param _delegationSig Signed hash of these input parameters so admin can submit this on behalf of a user
*/
function moveTokensToEscrowLockupFor(
address _sender,
uint256 _amount,
bytes32 _nonce,
bytes _delegationSig
) public onlyMarketplaceAdmin
bytes32 constant LOCKUP_TOKENS_FOR = keccak256(
"LockupTokensFor(address sender,uint256 amount,bytes32 nonce)"
);
struct LockupTokensFor {
address sender;
uint256 amount;
bytes32 nonce;
}
function hash(LockupTokensFor request) internal pure returns (bytes32) {
return keccak256(abi.encode(
LOCKUP_TOKENS_FOR,
request.sender,
request.amount,
request.nonce
));
}
function generateLockupTokensDelegationSchemaHash(
address _sender,
uint256 _amount,
bytes32 _nonce
) external view returns (bytes32) {
return keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR,
hash(LockupTokensFor(
_sender,
_amount,
_nonce
))
)
);
}
Release tokens from tokenEscrow
and transfer back to the owner.
/**
* @notice Withdraw tokens from escrow back to requester
* @dev Authorized by a signTypedData signature by sender
* Sigs can only be used once. They contain a unique nonce
* So an action can be repeated, with a different signature
* @param _sender User locking up their tokens
* @param _amount Tokens to lock up
* @param _nonce Unique Id so signatures can't be replayed
* @param _delegationSig Signed hash of these input parameters so an admin can submit this on behalf of a user
*/
function releaseTokensFromEscrowFor(
address _sender,
uint256 _amount,
bytes32 _nonce,
bytes _delegationSig
) public;
Bloom’s protocol heavily relies on community voting to make important protocol decisions. Anyone can create a Poll in the Bloom network by interacting with the VotingCenter contract. You don’t have to have a Bloom account to create a poll, but the dApp will likely filter the polls it displays to include polls created by community members. Votes are associated with addresses. If a user has multiple addresses linked together, their total BLT balance across all addresses will be used to tally their influence. If Alice wanted to create a very simple poll within the Bloom network, it might look like this:
// This module exists in the repo!
const ipfsUtils = require(“./src/ipfs”);
// Create a connection to IFPS
const IPFS = require(“ipfs-mini”);
const ipfs = new IPFS({
host: “ipfs.infura.io”,
port: 5001,
protocol: “https”
});
// Poll data we want to store
const poll = {
title: “What kind of ice cream should I buy?”,
description: “I am hosting a party soon and I need to decide!”,
choices: [“Vanilla”, “Chocolate”, “Strawberry”]
};
const chainId = 1; // mainnet
// Write the data to IPFS
ipfs.addJSON(poll, (err, ipfsMultihash) => {
if (err) throw err;
// On success, create the poll via the voting center
votingCenter.createPoll(
"Ice cream poll",
chainid,
ipfsUtils.toHex(ipfsMultihash),
poll.choices.length,
+new Date() / 1000 + 60 * 5, // start in 5 minutes
+new Date() / 1000 + 60 * 60 * 24 * 7 // end the poll in 1 week
);
});
A designed poll admin can submit votes on behalf of users in order to pay the transaction costs. A user signs a one-time-use signature with the vote information and sends it to the poll admin to vote on their behalf. A nonce is included in the signature to make each vote unique.
mapping (bytes32 => bool) public usedSignatures;
function voteFor(uint16 _choice, address _voter, bytes32 _nonce, bytes _delegationSig) external onlyPollAdmin
bytes32 public constant voteForSchemaHash =
keccak256(
abi.encodePacked(
"uint16 choice",
"address voter",
"bytes32 nonce",
"address poll"
)
);
function generateVoteForDelegationSchemaHash(
uint16 _choice,
address _voter,
bytes32 _nonce,
address _poll
) external view returns (bytes32) {
return keccak256(
abi.encodePacked(
voteForSchemaHash,
keccak256(
abi.encodePacked(
_choice,
_voter,
_nonce,
_poll
)
)
)
);
}
- Remove the current temporary ganache dir
rm -rf /tmp/ganache
- Recreate it
mkdir /tmp/ganache
- Start the rpc and run migrations
bin/rpc & bin/migrate-contracts
You can stop viapkill node
once3_print_addr.js
has finished - Traverse into the tmp dir
cd /tmp
- Run tar
tar -czf rpcSnapshot.tar.gz ganache