-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
All the logic relevant to specific ACLs have been moved separate files to src/components/ACLs. This should make it easier to test individual ACLs, and to att new ones.
- Loading branch information
Showing
13 changed files
with
661 additions
and
538 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { defineACL } from './common' | ||
import { VITE_CONTRACT_ACL_ALLOWALL } from '../../constants/config' | ||
import { denyWithReason } from '../InputFields' | ||
|
||
export const allowAll = defineACL({ | ||
value: 'acl_allowAll', | ||
label: 'Everybody', | ||
costEstimation: 0.1, | ||
useConfiguration: () => ({ | ||
fields: [], | ||
values: undefined, | ||
}), | ||
getAclOptions: () => [ | ||
'0x', // Empty bytes is passed | ||
{ | ||
address: VITE_CONTRACT_ACL_ALLOWALL, | ||
options: { allowAll: true }, | ||
}, | ||
], | ||
isThisMine: options => 'allowAll' in options.options, | ||
|
||
checkPermission: async (pollACL, daoAddress, proposalId, userAddress) => { | ||
const proof = new Uint8Array() | ||
const result = 0n !== (await pollACL.canVoteOnPoll(daoAddress, proposalId, userAddress, proof)) | ||
const canVote = result ? true : denyWithReason('some unknown reason') | ||
return { canVote, proof } | ||
}, | ||
} as const) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import { defineACL } from './common' | ||
import { VITE_CONTRACT_ACL_VOTERALLOWLIST } from '../../constants/config' | ||
import { abiEncode, isValidAddress } from '../../utils/poll.utils' | ||
import { denyWithReason, useTextArrayField } from '../InputFields' | ||
import { AclOptions } from '@oasisprotocol/blockvote-contracts' | ||
|
||
// Split a list of addresses by newLine, comma or space | ||
const splitAddresses = (addressSoup: string): string[] => | ||
addressSoup | ||
.split('\n') | ||
.flatMap(x => x.split(',')) | ||
.flatMap(x => x.split(' ')) | ||
.map(x => x.trim()) | ||
.filter(x => x.length > 0) | ||
|
||
export const allowList = defineACL({ | ||
value: 'acl_allowList', | ||
label: 'Address Whitelist', | ||
costEstimation: 0.1, | ||
description: 'You can specify a list of addresses that are allowed to vote.', | ||
useConfiguration: active => { | ||
const addresses = useTextArrayField({ | ||
name: 'addresses', | ||
label: 'Acceptable Addresses', | ||
visible: active, | ||
description: 'You can just copy-paste your list here', | ||
addItemLabel: 'Add address', | ||
removeItemLabel: 'Remove address', | ||
allowEmptyItems: [false, 'Please specify address, or remove this field!'], | ||
minItems: 1, | ||
allowDuplicates: [ | ||
false, | ||
['This address is repeated below.', 'The same address was already listed above!'], | ||
], | ||
itemValidator: value => | ||
value && !isValidAddress(value) ? "This doesn't seem to be a valid address." : undefined, | ||
onItemEdited: (index, value, me) => { | ||
if (value.indexOf(',') !== -1 || value.indexOf(' ') !== -1 || value.indexOf('\n') !== -1) { | ||
const addresses = splitAddresses(value) | ||
const newAddresses = [...me.value] | ||
for (let i = 0; i < addresses.length; i++) { | ||
newAddresses[index + i] = addresses[i] | ||
} | ||
me.setValue(newAddresses) | ||
} | ||
}, | ||
validateOnChange: true, | ||
showValidationSuccess: true, | ||
}) | ||
|
||
return { | ||
fields: [addresses], | ||
values: { | ||
addresses: addresses.value, | ||
}, | ||
} | ||
}, | ||
|
||
getAclOptions: (props): [string, AclOptions] => { | ||
if (!props.addresses) throw new Error('Internal errors: parameter mismatch, addresses missing.') | ||
return [ | ||
abiEncode(['address[]'], [props.addresses]), | ||
{ | ||
address: VITE_CONTRACT_ACL_VOTERALLOWLIST, | ||
options: { allowList: true }, | ||
}, | ||
] | ||
}, | ||
|
||
isThisMine: options => 'allowList' in options.options, | ||
|
||
checkPermission: async (pollACL, daoAddress, proposalId, userAddress) => { | ||
const proof = new Uint8Array() | ||
const explanation = 'This poll is only for a predefined list of addresses.' | ||
const result = 0n !== (await pollACL.canVoteOnPoll(daoAddress, proposalId, userAddress, proof)) | ||
// console.log("whiteListAcl check:", result) | ||
const canVote = result ? true : denyWithReason('you are not on the list of allowed addresses') | ||
return { canVote, explanation, proof } | ||
}, | ||
} as const) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import { Choice, DecisionWithReason, FieldConfiguration } from '../InputFields' | ||
import { AclOptions, IPollACL } from '@oasisprotocol/blockvote-contracts' | ||
import { BytesLike } from 'ethers' | ||
export type StatusUpdater = (status: string | undefined) => void | ||
|
||
/** | ||
* This data structure describes an ACL | ||
*/ | ||
export type ACL<Name, ConfigInputValues, Options extends AclOptions, Extra> = Choice<Name> & { | ||
/** | ||
* Estimated cost per vote | ||
* | ||
* This is used for setting up gasless voting. | ||
*/ | ||
costEstimation: number | ||
|
||
/** | ||
* Specify the fields and values needed for configuring the ACL when creating a poll | ||
*/ | ||
useConfiguration: (selected: boolean) => { fields: FieldConfiguration; values: ConfigInputValues } | ||
|
||
/** | ||
* Compose the ACL options when creating a poll | ||
*/ | ||
getAclOptions: | ||
| ((config: ConfigInputValues, statusUpdater?: StatusUpdater) => [string, Options]) | ||
| ((config: ConfigInputValues, statusUpdater?: StatusUpdater) => Promise<[string, Options]>) | ||
|
||
/** | ||
* Attempt to recognize if this ACL is managing a given poll, based on ACL options | ||
* @param options | ||
*/ | ||
isThisMine: (options: AclOptions) => boolean | ||
|
||
/** | ||
* Determine if we can vote on this poll | ||
* | ||
* The actual contract is made available, this function just needs to interpret the result | ||
* and compose the required messages. | ||
*/ | ||
checkPermission: ( | ||
pollACL: IPollACL, | ||
daoAddress: string, | ||
proposalId: string, | ||
userAddress: string, | ||
options: Options, | ||
) => Promise< | ||
{ canVote: DecisionWithReason; explanation?: string; proof: BytesLike; error?: string } & Extra | ||
> | ||
} | ||
|
||
export function defineACL<Name, ConfigInputValues, Options extends AclOptions, Extra>( | ||
acl: ACL<Name, ConfigInputValues, Options, Extra>, | ||
): ACL<Name, ConfigInputValues, Options, Extra> { | ||
return acl | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
.explanation { | ||
font-style: normal; | ||
font-weight: 400; | ||
font-size: 15px; | ||
color: #414749; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { allowAll } from './allowAll' | ||
import { allowList } from './allowList' | ||
import { tokenHolder } from './tokenHolder' | ||
import { xchain } from './xchain' | ||
import { AclOptions } from '../../types' | ||
|
||
/** | ||
* The list of supported ACLs | ||
*/ | ||
export const acls = [allowAll, allowList, tokenHolder, xchain] as const | ||
|
||
/** | ||
* Find the ACL needed to manage a poll, based on ACL options | ||
*/ | ||
export const findACLForOptions = (options: AclOptions) => acls.find(acl => acl.isThisMine(options)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
import { defineACL } from './common' | ||
import { DecisionWithReason, denyWithReason, useLabel, useTextField } from '../InputFields' | ||
import { abiEncode, getSapphireTokenDetails, isValidAddress } from '../../utils/poll.utils' | ||
import { VITE_CONTRACT_ACL_TOKENHOLDER } from '../../constants/config' | ||
|
||
export const tokenHolder = defineACL({ | ||
value: 'acl_tokenHolder', | ||
costEstimation: 0.2, | ||
label: 'Holds Token on Sapphire', | ||
hidden: true, // We decided to hide this option, since this is not the focus | ||
useConfiguration: active => { | ||
const tokenAddress = useTextField({ | ||
name: 'tokenAddress', | ||
label: 'Token Address', | ||
visible: active, | ||
required: [true, 'Please specify the address of the token that is the key to this poll!'], | ||
validators: [ | ||
value => (!isValidAddress(value) ? "This doesn't seem to be a valid address." : undefined), | ||
async (value, controls) => { | ||
controls.updateStatus({ message: 'Fetching token details...' }) | ||
const details = await getSapphireTokenDetails(value) | ||
if (!details) { | ||
return "Can't find token details!" | ||
} | ||
tokenName.setValue(details.name) | ||
tokenSymbol.setValue(details.symbol) | ||
}, | ||
], | ||
validateOnChange: true, | ||
showValidationSuccess: true, | ||
}) | ||
|
||
const hasValidSapphireTokenAddress = | ||
tokenAddress.visible && tokenAddress.isValidated && !tokenAddress.hasProblems | ||
|
||
const tokenName = useLabel({ | ||
name: 'tokenName', | ||
visible: hasValidSapphireTokenAddress, | ||
label: 'Selected token:', | ||
initialValue: '', | ||
}) | ||
|
||
const tokenSymbol = useLabel({ | ||
name: 'tokenSymbol', | ||
visible: hasValidSapphireTokenAddress, | ||
label: 'Symbol:', | ||
initialValue: '', | ||
}) | ||
|
||
return { | ||
fields: [tokenAddress, [tokenName, tokenSymbol]], | ||
values: { | ||
tokenAddress: tokenAddress.value, | ||
}, | ||
} | ||
}, | ||
|
||
getAclOptions: props => { | ||
if (!props.tokenAddress) throw new Error('Internal errors: parameter mismatch, addresses missing.') | ||
return [ | ||
abiEncode(['address'], [props.tokenAddress]), | ||
{ | ||
address: VITE_CONTRACT_ACL_TOKENHOLDER, | ||
options: { token: props.tokenAddress }, | ||
}, | ||
] | ||
}, | ||
|
||
isThisMine: options => 'token' in options.options, | ||
|
||
checkPermission: async (pollACL, daoAddress, proposalId, userAddress, options) => { | ||
const tokenAddress = options.options.token | ||
const tokenInfo = await getSapphireTokenDetails(tokenAddress) | ||
const explanation = `You need to hold some ${tokenInfo?.name ?? 'specific'} token (on the Sapphire network) to vote.` | ||
const proof = new Uint8Array() | ||
let canVote: DecisionWithReason | ||
try { | ||
const result = 0n !== (await pollACL.canVoteOnPoll(daoAddress, proposalId, userAddress, proof)) | ||
// console.log("tokenHolderAcl check:", result) | ||
if (result) { | ||
canVote = true | ||
} else { | ||
canVote = denyWithReason(`you don't hold any ${tokenInfo?.name} tokens`) | ||
} | ||
} catch { | ||
canVote = denyWithReason(`you don't hold any ${tokenInfo?.name} tokens`) | ||
} | ||
return { canVote, explanation, proof } | ||
}, | ||
} as const) |
Oops, something went wrong.