@ -0,0 +1,962 @@ |
||||
export const GROTH16_VERIFIER = `// SPDX-License-Identifier: GPL-3.0
|
||||
/* |
||||
Copyright 2021 0KIMS association. |
||||
|
||||
This file is generated with [snarkJS](https://github.com/iden3/snarkjs).
|
||||
|
||||
snarkJS is a free software: you can redistribute it and/or modify it |
||||
under the terms of the GNU General Public License as published by |
||||
the Free Software Foundation, either version 3 of the License, or |
||||
(at your option) any later version. |
||||
|
||||
snarkJS is distributed in the hope that it will be useful, but WITHOUT |
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
||||
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public |
||||
License for more details. |
||||
|
||||
You should have received a copy of the GNU General Public License |
||||
along with snarkJS. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
|
||||
pragma solidity >=0.7.0 <0.9.0; |
||||
|
||||
contract Groth16Verifier { |
||||
// Scalar field size
|
||||
uint256 constant r = 21888242871839275222246405745257275088548364400416034343698204186575808495617; |
||||
// Base field size
|
||||
uint256 constant q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; |
||||
|
||||
// Verification Key data
|
||||
uint256 constant alphax = <%= vk_alpha_1[0] %>; |
||||
uint256 constant alphay = <%= vk_alpha_1[1] %>; |
||||
uint256 constant betax1 = <%= vk_beta_2[0][1] %>; |
||||
uint256 constant betax2 = <%= vk_beta_2[0][0] %>; |
||||
uint256 constant betay1 = <%= vk_beta_2[1][1] %>; |
||||
uint256 constant betay2 = <%= vk_beta_2[1][0] %>; |
||||
uint256 constant gammax1 = <%= vk_gamma_2[0][1] %>; |
||||
uint256 constant gammax2 = <%= vk_gamma_2[0][0] %>; |
||||
uint256 constant gammay1 = <%= vk_gamma_2[1][1] %>; |
||||
uint256 constant gammay2 = <%= vk_gamma_2[1][0] %>; |
||||
uint256 constant deltax1 = <%= vk_delta_2[0][1] %>; |
||||
uint256 constant deltax2 = <%= vk_delta_2[0][0] %>; |
||||
uint256 constant deltay1 = <%= vk_delta_2[1][1] %>; |
||||
uint256 constant deltay2 = <%= vk_delta_2[1][0] %>; |
||||
|
||||
<% for (let i=0; i<IC.length; i++) { %> |
||||
uint256 constant IC<%=i%>x = <%=IC[i][0]%>; |
||||
uint256 constant IC<%=i%>y = <%=IC[i][1]%>; |
||||
<% } %> |
||||
|
||||
// Memory data
|
||||
uint16 constant pVk = 0; |
||||
uint16 constant pPairing = 128; |
||||
|
||||
uint16 constant pLastMem = 896; |
||||
|
||||
function verifyProof(uint[2] calldata _pA, uint[2][2] calldata _pB, uint[2] calldata _pC, uint[<%=IC.length-1%>] calldata _pubSignals) public view returns (bool) { |
||||
assembly { |
||||
function checkField(v) { |
||||
if iszero(lt(v, q)) { |
||||
mstore(0, 0) |
||||
return(0, 0x20) |
||||
} |
||||
} |
||||
|
||||
// G1 function to multiply a G1 value(x,y) to value in an address
|
||||
function g1_mulAccC(pR, x, y, s) { |
||||
let success |
||||
let mIn := mload(0x40) |
||||
mstore(mIn, x) |
||||
mstore(add(mIn, 32), y) |
||||
mstore(add(mIn, 64), s) |
||||
|
||||
success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64) |
||||
|
||||
if iszero(success) { |
||||
mstore(0, 0) |
||||
return(0, 0x20) |
||||
} |
||||
|
||||
mstore(add(mIn, 64), mload(pR)) |
||||
mstore(add(mIn, 96), mload(add(pR, 32))) |
||||
|
||||
success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64) |
||||
|
||||
if iszero(success) { |
||||
mstore(0, 0) |
||||
return(0, 0x20) |
||||
} |
||||
} |
||||
|
||||
function checkPairing(pA, pB, pC, pubSignals, pMem) -> isOk { |
||||
let _pPairing := add(pMem, pPairing) |
||||
let _pVk := add(pMem, pVk) |
||||
|
||||
mstore(_pVk, IC0x) |
||||
mstore(add(_pVk, 32), IC0y) |
||||
|
||||
// Compute the linear combination vk_x
|
||||
<% for (let i = 1; i <= nPublic; i++) { %> |
||||
g1_mulAccC(_pVk, IC<%=i%>x, IC<%=i%>y, calldataload(add(pubSignals, <%=(i-1)*32%>))) |
||||
<% } %> |
||||
|
||||
// -A
|
||||
mstore(_pPairing, calldataload(pA)) |
||||
mstore(add(_pPairing, 32), mod(sub(q, calldataload(add(pA, 32))), q)) |
||||
|
||||
// B
|
||||
mstore(add(_pPairing, 64), calldataload(pB)) |
||||
mstore(add(_pPairing, 96), calldataload(add(pB, 32))) |
||||
mstore(add(_pPairing, 128), calldataload(add(pB, 64))) |
||||
mstore(add(_pPairing, 160), calldataload(add(pB, 96))) |
||||
|
||||
// alpha1
|
||||
mstore(add(_pPairing, 192), alphax) |
||||
mstore(add(_pPairing, 224), alphay) |
||||
|
||||
// beta2
|
||||
mstore(add(_pPairing, 256), betax1) |
||||
mstore(add(_pPairing, 288), betax2) |
||||
mstore(add(_pPairing, 320), betay1) |
||||
mstore(add(_pPairing, 352), betay2) |
||||
|
||||
// vk_x
|
||||
mstore(add(_pPairing, 384), mload(add(pMem, pVk))) |
||||
mstore(add(_pPairing, 416), mload(add(pMem, add(pVk, 32)))) |
||||
|
||||
|
||||
// gamma2
|
||||
mstore(add(_pPairing, 448), gammax1) |
||||
mstore(add(_pPairing, 480), gammax2) |
||||
mstore(add(_pPairing, 512), gammay1) |
||||
mstore(add(_pPairing, 544), gammay2) |
||||
|
||||
// C
|
||||
mstore(add(_pPairing, 576), calldataload(pC)) |
||||
mstore(add(_pPairing, 608), calldataload(add(pC, 32))) |
||||
|
||||
// delta2
|
||||
mstore(add(_pPairing, 640), deltax1) |
||||
mstore(add(_pPairing, 672), deltax2) |
||||
mstore(add(_pPairing, 704), deltay1) |
||||
mstore(add(_pPairing, 736), deltay2) |
||||
|
||||
|
||||
let success := staticcall(sub(gas(), 2000), 8, _pPairing, 768, _pPairing, 0x20) |
||||
|
||||
isOk := and(success, mload(_pPairing)) |
||||
} |
||||
|
||||
let pMem := mload(0x40) |
||||
mstore(0x40, add(pMem, pLastMem)) |
||||
|
||||
// Validate that all evaluations ∈ F
|
||||
<% for (let i=0; i<IC.length; i++) { %> |
||||
checkField(calldataload(add(_pubSignals, <%=i*32%>))) |
||||
<% } %> |
||||
|
||||
// Validate all evaluations
|
||||
let isValid := checkPairing(_pA, _pB, _pC, _pubSignals, pMem) |
||||
|
||||
mstore(0, isValid) |
||||
return(0, 0x20) |
||||
} |
||||
} |
||||
}` |
||||
|
||||
export const PLONK_VERIFIER = `// SPDX-License-Identifier: GPL-3.0
|
||||
/* |
||||
Copyright 2021 0KIMS association. |
||||
|
||||
This file is generated with [snarkJS](https://github.com/iden3/snarkjs).
|
||||
|
||||
snarkJS is a free software: you can redistribute it and/or modify it |
||||
under the terms of the GNU General Public License as published by |
||||
the Free Software Foundation, either version 3 of the License, or |
||||
(at your option) any later version. |
||||
|
||||
snarkJS is distributed in the hope that it will be useful, but WITHOUT |
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
||||
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public |
||||
License for more details. |
||||
|
||||
You should have received a copy of the GNU General Public License |
||||
along with snarkJS. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
|
||||
|
||||
pragma solidity >=0.7.0 <0.9.0; |
||||
|
||||
import "hardhat/console.sol"; |
||||
|
||||
contract PlonkVerifier { |
||||
// Omega
|
||||
uint256 constant w1 = <%=w%>;
|
||||
// Scalar field size
|
||||
uint256 constant q = 21888242871839275222246405745257275088548364400416034343698204186575808495617; |
||||
// Base field size
|
||||
uint256 constant qf = 21888242871839275222246405745257275088696311157297823662689037894645226208583; |
||||
|
||||
// [1]_1
|
||||
uint256 constant G1x = 1; |
||||
uint256 constant G1y = 2; |
||||
// [1]_2
|
||||
uint256 constant G2x1 = 10857046999023057135944570762232829481370756359578518086990519993285655852781; |
||||
uint256 constant G2x2 = 11559732032986387107991004021392285783925812861821192530917403151452391805634; |
||||
uint256 constant G2y1 = 8495653923123431417604973247489272438418190587263600148770280649306958101930; |
||||
uint256 constant G2y2 = 4082367875863433681332203403145435568316851327593401208105741076214120093531; |
||||
|
||||
// Verification Key data
|
||||
uint32 constant n = <%=2**power%>; |
||||
uint16 constant nPublic = <%=nPublic%>; |
||||
uint16 constant nLagrange = <%=Math.max(nPublic, 1)%>; |
||||
|
||||
uint256 constant Qmx = <%=Qm[0]%>; |
||||
uint256 constant Qmy = <%=Qm[0] == "0" ? "0" : Qm[1]%>; |
||||
uint256 constant Qlx = <%=Ql[0]%>; |
||||
uint256 constant Qly = <%=Ql[0] == "0" ? "0" : Ql[1]%>; |
||||
uint256 constant Qrx = <%=Qr[0]%>; |
||||
uint256 constant Qry = <%=Qr[0] == "0" ? "0" : Qr[1]%>; |
||||
uint256 constant Qox = <%=Qo[0]%>; |
||||
uint256 constant Qoy = <%=Qo[0] == "0" ? "0" : Qo[1]%>; |
||||
uint256 constant Qcx = <%=Qc[0]%>; |
||||
uint256 constant Qcy = <%=Qc[0] == "0" ? "0" : Qc[1]%>; |
||||
uint256 constant S1x = <%=S1[0]%>; |
||||
uint256 constant S1y = <%=S1[0] == "0" ? "0" : S1[1]%>; |
||||
uint256 constant S2x = <%=S2[0]%>; |
||||
uint256 constant S2y = <%=S2[0] == "0" ? "0" : S2[1]%>; |
||||
uint256 constant S3x = <%=S3[0]%>; |
||||
uint256 constant S3y = <%=S3[0] == "0" ? "0" : S3[1]%>; |
||||
uint256 constant k1 = <%=k1%>; |
||||
uint256 constant k2 = <%=k2%>; |
||||
uint256 constant X2x1 = <%=X_2[0][0]%>; |
||||
uint256 constant X2x2 = <%=X_2[0][1]%>; |
||||
uint256 constant X2y1 = <%=X_2[1][0]%>; |
||||
uint256 constant X2y2 = <%=X_2[1][1]%>; |
||||
|
||||
// Proof calldata
|
||||
// Byte offset of every parameter of the calldata
|
||||
// Polynomial commitments
|
||||
uint16 constant pA = 4 + 0; |
||||
uint16 constant pB = 4 + 64; |
||||
uint16 constant pC = 4 + 128; |
||||
uint16 constant pZ = 4 + 192; |
||||
uint16 constant pT1 = 4 + 256; |
||||
uint16 constant pT2 = 4 + 320; |
||||
uint16 constant pT3 = 4 + 384; |
||||
uint16 constant pWxi = 4 + 448; |
||||
uint16 constant pWxiw = 4 + 512; |
||||
// Opening evaluations
|
||||
uint16 constant pEval_a = 4 + 576; |
||||
uint16 constant pEval_b = 4 + 608; |
||||
uint16 constant pEval_c = 4 + 640; |
||||
uint16 constant pEval_s1 = 4 + 672; |
||||
uint16 constant pEval_s2 = 4 + 704; |
||||
uint16 constant pEval_zw = 4 + 736; |
||||
|
||||
// Memory data
|
||||
// Challenges
|
||||
uint16 constant pAlpha = 0; |
||||
uint16 constant pBeta = 32; |
||||
uint16 constant pGamma = 64; |
||||
uint16 constant pXi = 96; |
||||
uint16 constant pXin = 128; |
||||
uint16 constant pBetaXi = 160; |
||||
uint16 constant pV1 = 192; |
||||
uint16 constant pV2 = 224; |
||||
uint16 constant pV3 = 256; |
||||
uint16 constant pV4 = 288; |
||||
uint16 constant pV5 = 320; |
||||
uint16 constant pU = 352; |
||||
|
||||
uint16 constant pPI = 384; |
||||
uint16 constant pEval_r0 = 416; |
||||
uint16 constant pD = 448; |
||||
uint16 constant pF = 512; |
||||
uint16 constant pE = 576; |
||||
uint16 constant pTmp = 640; |
||||
uint16 constant pAlpha2 = 704; |
||||
uint16 constant pZh = 736; |
||||
uint16 constant pZhInv = 768; |
||||
|
||||
<% for (let i=1; i<=Math.max(nPublic, 1); i++) { %> |
||||
uint16 constant pEval_l<%=i%> = <%=768+i*32%>; |
||||
<% } %> |
||||
<% let pLastMem = 800+32*Math.max(nPublic,1) %> |
||||
|
||||
uint16 constant lastMem = <%=pLastMem%>; |
||||
|
||||
function verifyProof(uint256[24] calldata _proof, uint256[<%=nPublic%>] calldata _pubSignals) public view returns (bool) { |
||||
assembly { |
||||
/////////
|
||||
// Computes the inverse using the extended euclidean algorithm
|
||||
/////////
|
||||
function inverse(a, q) -> inv { |
||||
let t := 0
|
||||
let newt := 1 |
||||
let r := q
|
||||
let newr := a |
||||
let quotient |
||||
let aux |
||||
|
||||
for { } newr { } { |
||||
quotient := sdiv(r, newr) |
||||
aux := sub(t, mul(quotient, newt)) |
||||
t:= newt |
||||
newt:= aux |
||||
|
||||
aux := sub(r,mul(quotient, newr)) |
||||
r := newr |
||||
newr := aux |
||||
} |
||||
|
||||
if gt(r, 1) { revert(0,0) } |
||||
if slt(t, 0) { t:= add(t, q) } |
||||
|
||||
inv := t |
||||
} |
||||
|
||||
///////
|
||||
// Computes the inverse of an array of values
|
||||
// See https://vitalik.ca/general/2018/07/21/starks_part_3.html in section where explain fields operations
|
||||
//////
|
||||
function inverseArray(pVals, n) { |
||||
|
||||
let pAux := mload(0x40) // Point to the next free position
|
||||
let pIn := pVals |
||||
let lastPIn := add(pVals, mul(n, 32)) // Read n elements
|
||||
let acc := mload(pIn) // Read the first element
|
||||
pIn := add(pIn, 32) // Point to the second element
|
||||
let inv |
||||
|
||||
|
||||
for { } lt(pIn, lastPIn) {
|
||||
pAux := add(pAux, 32)
|
||||
pIn := add(pIn, 32) |
||||
}
|
||||
{ |
||||
mstore(pAux, acc) |
||||
acc := mulmod(acc, mload(pIn), q) |
||||
} |
||||
acc := inverse(acc, q) |
||||
|
||||
// At this point pAux pint to the next free position we subtract 1 to point to the last used
|
||||
pAux := sub(pAux, 32) |
||||
// pIn points to the n+1 element, we subtract to point to n
|
||||
pIn := sub(pIn, 32) |
||||
lastPIn := pVals // We don't process the first element
|
||||
for { } gt(pIn, lastPIn) {
|
||||
pAux := sub(pAux, 32)
|
||||
pIn := sub(pIn, 32) |
||||
}
|
||||
{ |
||||
inv := mulmod(acc, mload(pAux), q) |
||||
acc := mulmod(acc, mload(pIn), q) |
||||
mstore(pIn, inv) |
||||
} |
||||
// pIn points to first element, we just set it.
|
||||
mstore(pIn, acc) |
||||
} |
||||
|
||||
function checkField(v) { |
||||
if iszero(lt(v, q)) { |
||||
mstore(0, 0) |
||||
return(0,0x20) |
||||
} |
||||
} |
||||
|
||||
function checkInput() { |
||||
checkField(calldataload(pEval_a)) |
||||
checkField(calldataload(pEval_b)) |
||||
checkField(calldataload(pEval_c)) |
||||
checkField(calldataload(pEval_s1)) |
||||
checkField(calldataload(pEval_s2)) |
||||
checkField(calldataload(pEval_zw)) |
||||
} |
||||
|
||||
function calculateChallenges(pMem, pPublic) { |
||||
let beta |
||||
let aux |
||||
|
||||
let mIn := mload(0x40) // Pointer to the next free memory position
|
||||
|
||||
// Compute challenge.beta & challenge.gamma
|
||||
mstore(mIn, Qmx) |
||||
mstore(add(mIn, 32), Qmy) |
||||
mstore(add(mIn, 64), Qlx) |
||||
mstore(add(mIn, 96), Qly) |
||||
mstore(add(mIn, 128), Qrx) |
||||
mstore(add(mIn, 160), Qry) |
||||
mstore(add(mIn, 192), Qox) |
||||
mstore(add(mIn, 224), Qoy) |
||||
mstore(add(mIn, 256), Qcx) |
||||
mstore(add(mIn, 288), Qcy) |
||||
mstore(add(mIn, 320), S1x) |
||||
mstore(add(mIn, 352), S1y) |
||||
mstore(add(mIn, 384), S2x) |
||||
mstore(add(mIn, 416), S2y) |
||||
mstore(add(mIn, 448), S3x) |
||||
mstore(add(mIn, 480), S3y) |
||||
|
||||
<%for (let i=0; i<nPublic;i++) {%> |
||||
mstore(add(mIn, <%= 512 + i*32 %>), calldataload(add(pPublic, <%=i*32%>))) |
||||
<%}%> |
||||
mstore(add(mIn, <%= 512 + nPublic*32 + 0 %> ), calldataload(pA)) |
||||
mstore(add(mIn, <%= 512 + nPublic*32 + 32 %> ), calldataload(add(pA, 32))) |
||||
mstore(add(mIn, <%= 512 + nPublic*32 + 64 %> ), calldataload(pB)) |
||||
mstore(add(mIn, <%= 512 + nPublic*32 + 96 %> ), calldataload(add(pB, 32))) |
||||
mstore(add(mIn, <%= 512 + nPublic*32 + 128 %> ), calldataload(pC)) |
||||
mstore(add(mIn, <%= 512 + nPublic*32 + 160 %> ), calldataload(add(pC, 32))) |
||||
|
||||
beta := mod(keccak256(mIn, <%= 704 + 32 * nPublic %>), q)
|
||||
mstore(add(pMem, pBeta), beta) |
||||
|
||||
// challenges.gamma
|
||||
mstore(add(pMem, pGamma), mod(keccak256(add(pMem, pBeta), 32), q)) |
||||
|
||||
// challenges.alpha
|
||||
mstore(mIn, mload(add(pMem, pBeta))) |
||||
mstore(add(mIn, 32), mload(add(pMem, pGamma))) |
||||
mstore(add(mIn, 64), calldataload(pZ)) |
||||
mstore(add(mIn, 96), calldataload(add(pZ, 32))) |
||||
|
||||
aux := mod(keccak256(mIn, 128), q) |
||||
mstore(add(pMem, pAlpha), aux) |
||||
mstore(add(pMem, pAlpha2), mulmod(aux, aux, q)) |
||||
|
||||
// challenges.xi
|
||||
mstore(mIn, aux) |
||||
mstore(add(mIn, 32), calldataload(pT1)) |
||||
mstore(add(mIn, 64), calldataload(add(pT1, 32))) |
||||
mstore(add(mIn, 96), calldataload(pT2)) |
||||
mstore(add(mIn, 128), calldataload(add(pT2, 32))) |
||||
mstore(add(mIn, 160), calldataload(pT3)) |
||||
mstore(add(mIn, 192), calldataload(add(pT3, 32))) |
||||
|
||||
aux := mod(keccak256(mIn, 224), q) |
||||
mstore( add(pMem, pXi), aux) |
||||
|
||||
// challenges.v
|
||||
mstore(mIn, aux) |
||||
mstore(add(mIn, 32), calldataload(pEval_a)) |
||||
mstore(add(mIn, 64), calldataload(pEval_b)) |
||||
mstore(add(mIn, 96), calldataload(pEval_c)) |
||||
mstore(add(mIn, 128), calldataload(pEval_s1)) |
||||
mstore(add(mIn, 160), calldataload(pEval_s2)) |
||||
mstore(add(mIn, 192), calldataload(pEval_zw)) |
||||
|
||||
let v1 := mod(keccak256(mIn, 224), q) |
||||
mstore(add(pMem, pV1), v1) |
||||
|
||||
// challenges.beta * challenges.xi
|
||||
mstore(add(pMem, pBetaXi), mulmod(beta, aux, q)) |
||||
|
||||
// challenges.xi^n
|
||||
<%for (let i=0; i<power;i++) {%> |
||||
aux:= mulmod(aux, aux, q) |
||||
<%}%> |
||||
mstore(add(pMem, pXin), aux) |
||||
|
||||
// Zh
|
||||
aux:= mod(add(sub(aux, 1), q), q) |
||||
mstore(add(pMem, pZh), aux) |
||||
mstore(add(pMem, pZhInv), aux) // We will invert later together with lagrange pols
|
||||
|
||||
// challenges.v^2, challenges.v^3, challenges.v^4, challenges.v^5
|
||||
aux := mulmod(v1, v1, q) |
||||
mstore(add(pMem, pV2), aux) |
||||
aux := mulmod(aux, v1, q) |
||||
mstore(add(pMem, pV3), aux) |
||||
aux := mulmod(aux, v1, q) |
||||
mstore(add(pMem, pV4), aux) |
||||
aux := mulmod(aux, v1, q) |
||||
mstore(add(pMem, pV5), aux) |
||||
|
||||
// challenges.u
|
||||
mstore(mIn, calldataload(pWxi)) |
||||
mstore(add(mIn, 32), calldataload(add(pWxi, 32))) |
||||
mstore(add(mIn, 64), calldataload(pWxiw)) |
||||
mstore(add(mIn, 96), calldataload(add(pWxiw, 32))) |
||||
|
||||
mstore(add(pMem, pU), mod(keccak256(mIn, 128), q)) |
||||
} |
||||
|
||||
function calculateLagrange(pMem) { |
||||
let w := 1
|
||||
<% for (let i=1; i<=Math.max(nPublic, 1); i++) { %> |
||||
mstore( |
||||
add(pMem, pEval_l<%=i%>),
|
||||
mulmod( |
||||
n,
|
||||
mod( |
||||
add( |
||||
sub( |
||||
mload(add(pMem, pXi)),
|
||||
w |
||||
),
|
||||
q |
||||
), |
||||
q |
||||
),
|
||||
q |
||||
) |
||||
) |
||||
<% if (i<Math.max(nPublic, 1)) { %> |
||||
w := mulmod(w, w1, q) |
||||
<% } %> |
||||
<% } %> |
||||
|
||||
inverseArray(add(pMem, pZhInv), <%=Math.max(nPublic, 1)+1%> ) |
||||
|
||||
let zh := mload(add(pMem, pZh)) |
||||
w := 1 |
||||
<% for (let i=1; i<=Math.max(nPublic, 1); i++) { %> |
||||
<% if (i==1) { %> |
||||
mstore( |
||||
add(pMem, pEval_l1 ),
|
||||
mulmod( |
||||
mload(add(pMem, pEval_l1 )), |
||||
zh, |
||||
q |
||||
) |
||||
) |
||||
<% } else { %> |
||||
mstore( |
||||
add(pMem, pEval_l<%=i%>),
|
||||
mulmod( |
||||
w, |
||||
mulmod( |
||||
mload(add(pMem, pEval_l<%=i%>)), |
||||
zh, |
||||
q |
||||
), |
||||
q |
||||
) |
||||
) |
||||
<% } %> |
||||
<% if (i<Math.max(nPublic, 1)) { %> |
||||
w := mulmod(w, w1, q) |
||||
<% } %> |
||||
<% } %> |
||||
|
||||
|
||||
} |
||||
|
||||
function calculatePI(pMem, pPub) { |
||||
let pl := 0 |
||||
|
||||
<% for (let i=0; i<nPublic; i++) { %>
|
||||
pl := mod( |
||||
add( |
||||
sub( |
||||
pl,
|
||||
mulmod( |
||||
mload(add(pMem, pEval_l<%=i+1%>)), |
||||
calldataload(add(pPub, <%=i*32%>)), |
||||
q |
||||
) |
||||
), |
||||
q |
||||
), |
||||
q |
||||
) |
||||
<% } %> |
||||
|
||||
mstore(add(pMem, pPI), pl) |
||||
} |
||||
|
||||
function calculateR0(pMem) { |
||||
let e1 := mload(add(pMem, pPI)) |
||||
|
||||
let e2 := mulmod(mload(add(pMem, pEval_l1)), mload(add(pMem, pAlpha2)), q) |
||||
|
||||
let e3a := addmod( |
||||
calldataload(pEval_a), |
||||
mulmod(mload(add(pMem, pBeta)), calldataload(pEval_s1), q), |
||||
q) |
||||
e3a := addmod(e3a, mload(add(pMem, pGamma)), q) |
||||
|
||||
let e3b := addmod( |
||||
calldataload(pEval_b), |
||||
mulmod(mload(add(pMem, pBeta)), calldataload(pEval_s2), q), |
||||
q) |
||||
e3b := addmod(e3b, mload(add(pMem, pGamma)), q) |
||||
|
||||
let e3c := addmod( |
||||
calldataload(pEval_c), |
||||
mload(add(pMem, pGamma)), |
||||
q) |
||||
|
||||
let e3 := mulmod(mulmod(e3a, e3b, q), e3c, q) |
||||
e3 := mulmod(e3, calldataload(pEval_zw), q) |
||||
e3 := mulmod(e3, mload(add(pMem, pAlpha)), q) |
||||
|
||||
let r0 := addmod(e1, mod(sub(q, e2), q), q) |
||||
r0 := addmod(r0, mod(sub(q, e3), q), q) |
||||
|
||||
mstore(add(pMem, pEval_r0) , r0) |
||||
} |
||||
|
||||
function g1_set(pR, pP) { |
||||
mstore(pR, mload(pP)) |
||||
mstore(add(pR, 32), mload(add(pP,32))) |
||||
}
|
||||
|
||||
function g1_setC(pR, x, y) { |
||||
mstore(pR, x) |
||||
mstore(add(pR, 32), y) |
||||
} |
||||
|
||||
function g1_calldataSet(pR, pP) { |
||||
mstore(pR, calldataload(pP)) |
||||
mstore(add(pR, 32), calldataload(add(pP, 32))) |
||||
} |
||||
|
||||
function g1_acc(pR, pP) { |
||||
let mIn := mload(0x40) |
||||
mstore(mIn, mload(pR)) |
||||
mstore(add(mIn,32), mload(add(pR, 32))) |
||||
mstore(add(mIn,64), mload(pP)) |
||||
mstore(add(mIn,96), mload(add(pP, 32))) |
||||
|
||||
let success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64) |
||||
|
||||
if iszero(success) { |
||||
mstore(0, 0) |
||||
return(0,0x20) |
||||
} |
||||
} |
||||
|
||||
function g1_mulAcc(pR, pP, s) { |
||||
let success |
||||
let mIn := mload(0x40) |
||||
mstore(mIn, mload(pP)) |
||||
mstore(add(mIn,32), mload(add(pP, 32))) |
||||
mstore(add(mIn,64), s) |
||||
|
||||
success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64) |
||||
|
||||
if iszero(success) { |
||||
mstore(0, 0) |
||||
return(0,0x20) |
||||
} |
||||
|
||||
mstore(add(mIn,64), mload(pR)) |
||||
mstore(add(mIn,96), mload(add(pR, 32))) |
||||
|
||||
success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64) |
||||
|
||||
if iszero(success) { |
||||
mstore(0, 0) |
||||
return(0,0x20) |
||||
} |
||||
|
||||
} |
||||
|
||||
function g1_mulAccC(pR, x, y, s) { |
||||
let success |
||||
let mIn := mload(0x40) |
||||
mstore(mIn, x) |
||||
mstore(add(mIn,32), y) |
||||
mstore(add(mIn,64), s) |
||||
|
||||
success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64) |
||||
|
||||
if iszero(success) { |
||||
mstore(0, 0) |
||||
return(0,0x20) |
||||
} |
||||
|
||||
mstore(add(mIn,64), mload(pR)) |
||||
mstore(add(mIn,96), mload(add(pR, 32))) |
||||
|
||||
success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64) |
||||
|
||||
if iszero(success) { |
||||
mstore(0, 0) |
||||
return(0,0x20) |
||||
} |
||||
} |
||||
|
||||
function g1_mulSetC(pR, x, y, s) { |
||||
let success |
||||
let mIn := mload(0x40) |
||||
mstore(mIn, x) |
||||
mstore(add(mIn,32), y) |
||||
mstore(add(mIn,64), s) |
||||
|
||||
success := staticcall(sub(gas(), 2000), 7, mIn, 96, pR, 64) |
||||
|
||||
if iszero(success) { |
||||
mstore(0, 0) |
||||
return(0,0x20) |
||||
} |
||||
} |
||||
|
||||
function g1_mulSet(pR, pP, s) { |
||||
g1_mulSetC(pR, mload(pP), mload(add(pP, 32)), s) |
||||
} |
||||
|
||||
function calculateD(pMem) { |
||||
let _pD:= add(pMem, pD) |
||||
let gamma := mload(add(pMem, pGamma)) |
||||
let mIn := mload(0x40) |
||||
mstore(0x40, add(mIn, 256)) // d1, d2, d3 & d4 (4*64 bytes)
|
||||
|
||||
g1_setC(_pD, Qcx, Qcy) |
||||
g1_mulAccC(_pD, Qmx, Qmy, mulmod(calldataload(pEval_a), calldataload(pEval_b), q)) |
||||
g1_mulAccC(_pD, Qlx, Qly, calldataload(pEval_a)) |
||||
g1_mulAccC(_pD, Qrx, Qry, calldataload(pEval_b)) |
||||
g1_mulAccC(_pD, Qox, Qoy, calldataload(pEval_c))
|
||||
|
||||
let betaxi := mload(add(pMem, pBetaXi)) |
||||
let val1 := addmod( |
||||
addmod(calldataload(pEval_a), betaxi, q), |
||||
gamma, q) |
||||
|
||||
let val2 := addmod( |
||||
addmod( |
||||
calldataload(pEval_b), |
||||
mulmod(betaxi, k1, q), |
||||
q), gamma, q) |
||||
|
||||
let val3 := addmod( |
||||
addmod( |
||||
calldataload(pEval_c), |
||||
mulmod(betaxi, k2, q), |
||||
q), gamma, q) |
||||
|
||||
let d2a := mulmod( |
||||
mulmod(mulmod(val1, val2, q), val3, q), |
||||
mload(add(pMem, pAlpha)), |
||||
q |
||||
) |
||||
|
||||
let d2b := mulmod( |
||||
mload(add(pMem, pEval_l1)), |
||||
mload(add(pMem, pAlpha2)), |
||||
q |
||||
) |
||||
|
||||
// We'll use mIn to save d2
|
||||
g1_calldataSet(add(mIn, 192), pZ) |
||||
g1_mulSet( |
||||
mIn, |
||||
add(mIn, 192), |
||||
addmod(addmod(d2a, d2b, q), mload(add(pMem, pU)), q)) |
||||
|
||||
|
||||
val1 := addmod( |
||||
addmod( |
||||
calldataload(pEval_a), |
||||
mulmod(mload(add(pMem, pBeta)), calldataload(pEval_s1), q), |
||||
q), gamma, q) |
||||
|
||||
val2 := addmod( |
||||
addmod( |
||||
calldataload(pEval_b), |
||||
mulmod(mload(add(pMem, pBeta)), calldataload(pEval_s2), q), |
||||
q), gamma, q) |
||||
|
||||
val3 := mulmod( |
||||
mulmod(mload(add(pMem, pAlpha)), mload(add(pMem, pBeta)), q), |
||||
calldataload(pEval_zw), q) |
||||
|
||||
|
||||
// We'll use mIn + 64 to save d3
|
||||
g1_mulSetC( |
||||
add(mIn, 64), |
||||
S3x, |
||||
S3y, |
||||
mulmod(mulmod(val1, val2, q), val3, q)) |
||||
|
||||
// We'll use mIn + 128 to save d4
|
||||
g1_calldataSet(add(mIn, 128), pT1) |
||||
|
||||
g1_mulAccC(add(mIn, 128), calldataload(pT2), calldataload(add(pT2, 32)), mload(add(pMem, pXin))) |
||||
let xin2 := mulmod(mload(add(pMem, pXin)), mload(add(pMem, pXin)), q) |
||||
g1_mulAccC(add(mIn, 128), calldataload(pT3), calldataload(add(pT3, 32)) , xin2) |
||||
|
||||
g1_mulSetC(add(mIn, 128), mload(add(mIn, 128)), mload(add(mIn, 160)), mload(add(pMem, pZh))) |
||||
|
||||
mstore(add(add(mIn, 64), 32), mod(sub(qf, mload(add(add(mIn, 64), 32))), qf)) |
||||
mstore(add(mIn, 160), mod(sub(qf, mload(add(mIn, 160))), qf)) |
||||
g1_acc(_pD, mIn) |
||||
g1_acc(_pD, add(mIn, 64)) |
||||
g1_acc(_pD, add(mIn, 128)) |
||||
} |
||||
|
||||
function calculateF(pMem) { |
||||
let p := add(pMem, pF) |
||||
|
||||
g1_set(p, add(pMem, pD)) |
||||
g1_mulAccC(p, calldataload(pA), calldataload(add(pA, 32)), mload(add(pMem, pV1))) |
||||
g1_mulAccC(p, calldataload(pB), calldataload(add(pB, 32)), mload(add(pMem, pV2))) |
||||
g1_mulAccC(p, calldataload(pC), calldataload(add(pC, 32)), mload(add(pMem, pV3))) |
||||
g1_mulAccC(p, S1x, S1y, mload(add(pMem, pV4))) |
||||
g1_mulAccC(p, S2x, S2y, mload(add(pMem, pV5))) |
||||
} |
||||
|
||||
function calculateE(pMem) { |
||||
let s := mod(sub(q, mload(add(pMem, pEval_r0))), q) |
||||
|
||||
s := addmod(s, mulmod(calldataload(pEval_a), mload(add(pMem, pV1)), q), q) |
||||
s := addmod(s, mulmod(calldataload(pEval_b), mload(add(pMem, pV2)), q), q) |
||||
s := addmod(s, mulmod(calldataload(pEval_c), mload(add(pMem, pV3)), q), q) |
||||
s := addmod(s, mulmod(calldataload(pEval_s1), mload(add(pMem, pV4)), q), q) |
||||
s := addmod(s, mulmod(calldataload(pEval_s2), mload(add(pMem, pV5)), q), q) |
||||
s := addmod(s, mulmod(calldataload(pEval_zw), mload(add(pMem, pU)), q), q) |
||||
|
||||
g1_mulSetC(add(pMem, pE), G1x, G1y, s) |
||||
} |
||||
|
||||
function checkPairing(pMem) -> isOk { |
||||
let mIn := mload(0x40) |
||||
mstore(0x40, add(mIn, 576)) // [0..383] = pairing data, [384..447] = pWxi, [448..512] = pWxiw
|
||||
|
||||
let _pWxi := add(mIn, 384) |
||||
let _pWxiw := add(mIn, 448) |
||||
let _aux := add(mIn, 512) |
||||
|
||||
g1_calldataSet(_pWxi, pWxi) |
||||
g1_calldataSet(_pWxiw, pWxiw) |
||||
|
||||
// A1
|
||||
g1_mulSet(mIn, _pWxiw, mload(add(pMem, pU))) |
||||
g1_acc(mIn, _pWxi) |
||||
mstore(add(mIn, 32), mod(sub(qf, mload(add(mIn, 32))), qf)) |
||||
|
||||
// [X]_2
|
||||
mstore(add(mIn,64), X2x2) |
||||
mstore(add(mIn,96), X2x1) |
||||
mstore(add(mIn,128), X2y2) |
||||
mstore(add(mIn,160), X2y1) |
||||
|
||||
// B1
|
||||
g1_mulSet(add(mIn, 192), _pWxi, mload(add(pMem, pXi))) |
||||
|
||||
let s := mulmod(mload(add(pMem, pU)), mload(add(pMem, pXi)), q) |
||||
s := mulmod(s, w1, q) |
||||
g1_mulSet(_aux, _pWxiw, s) |
||||
g1_acc(add(mIn, 192), _aux) |
||||
g1_acc(add(mIn, 192), add(pMem, pF)) |
||||
mstore(add(pMem, add(pE, 32)), mod(sub(qf, mload(add(pMem, add(pE, 32)))), qf)) |
||||
g1_acc(add(mIn, 192), add(pMem, pE)) |
||||
|
||||
// [1]_2
|
||||
mstore(add(mIn,256), G2x2) |
||||
mstore(add(mIn,288), G2x1) |
||||
mstore(add(mIn,320), G2y2) |
||||
mstore(add(mIn,352), G2y1) |
||||
|
||||
let success := staticcall(sub(gas(), 2000), 8, mIn, 384, mIn, 0x20) |
||||
|
||||
isOk := and(success, mload(mIn)) |
||||
} |
||||
|
||||
let pMem := mload(0x40) |
||||
mstore(0x40, add(pMem, lastMem)) |
||||
|
||||
checkInput() |
||||
calculateChallenges(pMem, _pubSignals) |
||||
calculateLagrange(pMem) |
||||
calculatePI(pMem, _pubSignals) |
||||
calculateR0(pMem) |
||||
calculateD(pMem) |
||||
calculateF(pMem) |
||||
calculateE(pMem) |
||||
let isValid := checkPairing(pMem) |
||||
|
||||
mstore(0x40, sub(pMem, lastMem)) |
||||
mstore(0, isValid) |
||||
return(0,0x20) |
||||
} |
||||
|
||||
} |
||||
}` |
||||
|
||||
export const PTAU_LIST = [ |
||||
{ |
||||
name: "final_8.ptau", |
||||
power: 8, |
||||
maxConstraint: "246", |
||||
ipfsHash: "QmNwT4UN6gT7vdDPNjmpShEVVbhi6C7tR6Y98X4aCT7sbq", |
||||
blake2bHash: null |
||||
}, |
||||
{ |
||||
name: "final_9.ptau", |
||||
power: 9, |
||||
maxConstraint: "512", |
||||
ipfsHash: "QmSBtobA8c27Yf2ypTpNtLxxxuadFzyQGksg7un2kN56yN", |
||||
blake2bHash: null |
||||
}, |
||||
{ |
||||
name: "final_10.ptau", |
||||
power: 10, |
||||
maxConstraint: "1k", |
||||
ipfsHash: "QmQ4dLmm3rRJKAm8CiVUedKu6xFNYsFD4UMVoSrD59jgHa", |
||||
blake2bHash: null |
||||
}, |
||||
{ |
||||
name: "final_11.ptau", |
||||
power: 11, |
||||
maxConstraint: "2k", |
||||
ipfsHash: "QmdRRJBznWaRnnpSnVcsdJ2RFQLxpVYbeMec3Fwf246MqU", |
||||
blake2bHash: null |
||||
}, |
||||
{ |
||||
name: "final_12.ptau", |
||||
power: 12, |
||||
maxConstraint: "4k", |
||||
ipfsHash: "Qmdge3jmta3qhGa61Da5UwVBcib6rgkqqHugQCgoT9hutS", |
||||
blake2bHash: null |
||||
}, |
||||
{ |
||||
name: "final_13.ptau", |
||||
power: 13, |
||||
maxConstraint: "8k", |
||||
ipfsHash: "QmUyXX6qYoExJ5pxyYiukmBSDkwqavUBBHaH2JDYVJX454", |
||||
blake2bHash: null |
||||
}, |
||||
{ |
||||
name: "final_14.ptau", |
||||
power: 14, |
||||
maxConstraint: "16k", |
||||
ipfsHash: "QmTiT4eiYz5KF7gQrDsgfCSTRv3wBPYJ4bRN1MmTRshpnW", |
||||
blake2bHash: null |
||||
}, |
||||
{ |
||||
name: "final_15.ptau", |
||||
power: 15, |
||||
maxConstraint: "32k", |
||||
ipfsHash: "QmeWPz6XMLX8HZwkRiiUffC5ziuiaHwNex6KUshpyLFpDg", |
||||
blake2bHash: null |
||||
}, |
||||
{ |
||||
name: "final_16.ptau", |
||||
power: 16, |
||||
maxConstraint: "64k", |
||||
ipfsHash: "QmVEbbVbBAgciBwGrNCx5HUJhQtatLVSEt54DZcrodYM3G", |
||||
blake2bHash: null |
||||
}, |
||||
{ |
||||
name: "final_17.ptau", |
||||
power: 17, |
||||
maxConstraint: "128k", |
||||
ipfsHash: "QmciCq5JcZQyTLvC9GRanrLBi82ZmSriq1Fr5zANkGHebf", |
||||
blake2bHash: null |
||||
}, |
||||
{ |
||||
name: "final_18.ptau", |
||||
power: 18, |
||||
maxConstraint: "256k", |
||||
ipfsHash: "QmRXbrYrrX2oA1FFRFivNi1TjhKoWv2x15g8PRFyV64UZP", |
||||
blake2bHash: null |
||||
}, |
||||
{ |
||||
name: "final_20.ptau", |
||||
power: 20, |
||||
maxConstraint: "1M", |
||||
ipfsHash: "QmQbrzmoZn5Ku6ENJmwZ1DMN1taAPgwLnp5TMfuSAz6tfh", |
||||
blake2bHash: null |
||||
}] |
@ -1,11 +1,9 @@ |
||||
import { CompileBtn } from "./compileBtn"; |
||||
import { R1CSBtn } from "./r1csBtn"; |
||||
import { CompileBtn } from "./compileBtn" |
||||
|
||||
export function CircuitActions () { |
||||
return ( |
||||
<div className="pb-3"> |
||||
<div className="pb-2"> |
||||
<CompileBtn /> |
||||
<R1CSBtn /> |
||||
</div> |
||||
) |
||||
} |
@ -1,36 +0,0 @@ |
||||
import { useState } from "react" |
||||
import { FormattedMessage } from "react-intl" |
||||
import { RenderIf, RenderIfNot } from "@remix-ui/helper" |
||||
|
||||
export function ConfigToggler ({ children }: { children: JSX.Element }) { |
||||
const [toggleExpander, setToggleExpander] = useState<boolean>(false) |
||||
|
||||
const toggleConfigurations = () => { |
||||
setToggleExpander(!toggleExpander) |
||||
} |
||||
|
||||
return ( |
||||
<div> |
||||
<div className="d-flex circuit_config_section justify-content-between" onClick={toggleConfigurations}> |
||||
<div className="d-flex"> |
||||
<label className="mt-1 circuit_config_section"> |
||||
<FormattedMessage id="circuit.advancedConfigurations" /> |
||||
</label> |
||||
</div> |
||||
<div> |
||||
<span data-id="scConfigExpander" onClick={toggleConfigurations}> |
||||
<RenderIf condition={toggleExpander}> |
||||
<i className="fas fa-angle-down" aria-hidden="true"></i> |
||||
</RenderIf> |
||||
<RenderIfNot condition={toggleExpander}> |
||||
<i className="fas fa-angle-right" aria-hidden="true"></i> |
||||
</RenderIfNot> |
||||
</span> |
||||
</div> |
||||
</div> |
||||
<RenderIf condition={toggleExpander}> |
||||
{ children } |
||||
</RenderIf> |
||||
</div> |
||||
) |
||||
} |
@ -0,0 +1,39 @@ |
||||
import { RenderIf } from "@remix-ui/helper" |
||||
import { FormattedMessage } from "react-intl" |
||||
import { generateProof } from "../actions" |
||||
import { CircuitAppContext } from "../contexts" |
||||
import { useContext } from "react" |
||||
|
||||
export function GenerateProof () { |
||||
const circuitApp = useContext(CircuitAppContext) |
||||
const status = circuitApp.appState.status |
||||
|
||||
return ( |
||||
<div className="flex-column d-flex"> |
||||
<div className="mt-2 custom-control custom-checkbox"> |
||||
<input |
||||
className="custom-control-input" |
||||
type="checkbox" |
||||
title="Export Verifier Calldata" |
||||
id="circuitExportVerifierCalldata" |
||||
onChange={() => circuitApp.dispatch({ type: 'SET_EXPORT_VERIFIER_CALLDATA', payload: !circuitApp.appState.exportVerifierCalldata })} |
||||
checked={circuitApp.appState.exportVerifierCalldata} |
||||
/> |
||||
<label className="form-check-label custom-control-label pt-1" htmlFor="circuitExportVerifierCalldata"> |
||||
<FormattedMessage id="circuit.exportVerifierCalldata" /> |
||||
</label> |
||||
</div> |
||||
<button |
||||
className="btn btn-secondary btn-block d-block w-100 text-break mb-1 mt-1" |
||||
onClick={() => generateProof(circuitApp.plugin, circuitApp.appState, circuitApp.dispatch)} |
||||
disabled={(status === "compiling") || (status === "computing") || (status === "proving") || (status === "exporting")} |
||||
data-id="compute_witness_btn" |
||||
> |
||||
<RenderIf condition={status === 'proving'}> |
||||
<i className="fas fa-sync fa-spin mr-2" aria-hidden="true"></i> |
||||
</RenderIf> |
||||
<FormattedMessage id="circuit.generateProof" /> |
||||
</button> |
||||
</div> |
||||
) |
||||
} |
@ -1,44 +0,0 @@ |
||||
import { CustomTooltip, RenderIf, RenderIfNot } from "@remix-ui/helper"; |
||||
import { useContext } from "react"; |
||||
import { CircuitAppContext } from "../contexts"; |
||||
import { FormattedMessage } from "react-intl"; |
||||
import { generateR1cs } from "../actions"; |
||||
|
||||
export function R1CSBtn () { |
||||
const { plugin, appState } = useContext(CircuitAppContext) |
||||
|
||||
return ( |
||||
<button |
||||
className="btn btn-secondary btn-block d-block w-100 text-break mb-1 mt-2" |
||||
onClick={() => { generateR1cs(plugin, appState) }} |
||||
disabled={(appState.filePath === "") || (appState.status === "compiling") || (appState.status === "generating") || (appState.status === "computing")} |
||||
data-id="generate_r1cs_btn" |
||||
> |
||||
<CustomTooltip |
||||
placement="auto" |
||||
tooltipId="overlay-tooltip-compile" |
||||
tooltipText={ |
||||
<div className="text-left"> |
||||
<div> |
||||
Outputs the constraints in r1cs format |
||||
</div> |
||||
</div> |
||||
} |
||||
> |
||||
<div className="d-flex align-items-center justify-content-center"> |
||||
<RenderIf condition={appState.status === 'generating'}> |
||||
<i className="fas fa-sync fa-spin mr-2" aria-hidden="true"></i> |
||||
</RenderIf> |
||||
<RenderIfNot condition={appState.status === 'generating'}> |
||||
<i className="fas fa-sync mr-2" aria-hidden="true"></i> |
||||
</RenderIfNot> |
||||
<div className="text-truncate overflow-hidden text-nowrap"> |
||||
<span> |
||||
<FormattedMessage id="circuit.generateR1cs" /> |
||||
</span> |
||||
</div> |
||||
</div> |
||||
</CustomTooltip> |
||||
</button> |
||||
) |
||||
} |
@ -0,0 +1,110 @@ |
||||
import { CustomTooltip } from "@remix-ui/helper" |
||||
import { FormattedMessage } from "react-intl" |
||||
import { SetupExportsBtn } from "./setupExportsBtn" |
||||
import { useContext } from "react" |
||||
import { CircuitAppContext } from "../contexts" |
||||
import { runSetupAndExport } from "../actions" |
||||
|
||||
export function SetupExports () { |
||||
const circuitApp = useContext(CircuitAppContext) |
||||
|
||||
return ( |
||||
<div className="flex-column"> |
||||
<div className="flex-column d-flex"> |
||||
<div className="mb-1 ml-0"> |
||||
<label className="circuit_inner_label form-check-label"> |
||||
<FormattedMessage id="circuit.provingScheme" /> |
||||
</label> |
||||
<div className="radio custom-control custom-radio mb-1 form-check"> |
||||
<input |
||||
type="radio" |
||||
className="align-middle custom-control-input" |
||||
name="circuitProvingScheme" |
||||
id="groth16ProvingScheme" |
||||
onClick={() => circuitApp.dispatch({ type: 'SET_PROVING_SCHEME', payload: 'groth16' })} |
||||
value='groth16' |
||||
checked={circuitApp.appState.provingScheme === 'groth16'} |
||||
readOnly |
||||
/> |
||||
<label className="form-check-label custom-control-label" data-id="groth16ProvingScheme" htmlFor="groth16ProvingScheme" style={{ paddingTop: '0.125rem' }}> |
||||
Groth16 |
||||
</label> |
||||
</div> |
||||
<div className="radio custom-control custom-radio form-check"> |
||||
<input |
||||
type="radio" |
||||
className="align-middle custom-control-input" |
||||
name="circuitProvingScheme" |
||||
id="plonkProvingScheme" |
||||
onClick={() => circuitApp.dispatch({ type: 'SET_PROVING_SCHEME', payload: 'plonk' })} |
||||
value='plonk' |
||||
checked={circuitApp.appState.provingScheme === 'plonk'} |
||||
readOnly |
||||
/> |
||||
<label className="form-check-label custom-control-label" data-id="plonkProvingScheme" htmlFor="plonkProvingScheme" style={{ paddingTop: '0.125rem' }}> |
||||
Plonk |
||||
</label> |
||||
</div> |
||||
</div> |
||||
<div className="mb-1 ml-0"> |
||||
<label className="circuit_inner_label form-check-label"> |
||||
<FormattedMessage id="circuit.ptau" /> |
||||
</label> |
||||
<CustomTooltip |
||||
placement={"auto"} |
||||
tooltipId="circuitPtauTooltip" |
||||
tooltipClasses="text-nowrap" |
||||
tooltipText={<span>{'To choose the from the list of ptau files'}</span>} |
||||
> |
||||
<div className="mb-1"> |
||||
<select |
||||
value={circuitApp.appState.ptauValue} |
||||
className="custom-select" |
||||
style={{ |
||||
pointerEvents: 'auto' |
||||
}} |
||||
onChange={(e) => circuitApp.dispatch({ type: 'SET_PTAU_VALUE', payload: e.target.value })} |
||||
data-id="circuitPtauSelect" |
||||
> |
||||
{ |
||||
circuitApp.appState.ptauList.map((ptau, index) => { |
||||
return ( |
||||
<option key={index} value={ptau.name} data-id={`dropdown-item-${ptau.name}`}>{`${ptau.name} (${ptau.maxConstraint} max constr.)`}</option> |
||||
) |
||||
}) |
||||
} |
||||
</select> |
||||
</div> |
||||
</CustomTooltip> |
||||
<div className="mt-2 custom-control custom-checkbox"> |
||||
<input |
||||
className="custom-control-input" |
||||
type="checkbox" |
||||
title="Export Verifier Contract" |
||||
id="circuitExportVerifierContract" |
||||
onChange={() => circuitApp.dispatch({ type: 'SET_EXPORT_VERIFICATION_CONTRACT', payload: !circuitApp.appState.exportVerificationContract })} |
||||
checked={circuitApp.appState.exportVerificationContract} |
||||
/> |
||||
<label className="form-check-label custom-control-label pt-1" htmlFor="circuitExportVerifierContract"> |
||||
<FormattedMessage id="circuit.exportVerifierContract" /> |
||||
</label> |
||||
</div> |
||||
<div className="mt-2 custom-control custom-checkbox"> |
||||
<input |
||||
className="custom-control-input" |
||||
type="checkbox" |
||||
title="Export Verification Key" |
||||
id="circuitExportVerificationKey" |
||||
onChange={() => circuitApp.dispatch({ type: 'SET_EXPORT_VERIFICATION_KEY', payload: !circuitApp.appState.exportVerificationKey })} |
||||
checked={circuitApp.appState.exportVerificationKey} |
||||
/> |
||||
<label className="form-check-label custom-control-label pt-1" htmlFor="circuitExportVerificationKey"> |
||||
<FormattedMessage id="circuit.exportVerificationKey" /> |
||||
</label> |
||||
</div> |
||||
<SetupExportsBtn handleRunSetup={() => runSetupAndExport(circuitApp.plugin, circuitApp.appState, circuitApp.dispatch)} status={circuitApp.appState.status} /> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
) |
||||
} |
@ -0,0 +1,34 @@ |
||||
import { CustomTooltip, RenderIf } from "@remix-ui/helper" |
||||
import { FormattedMessage } from "react-intl" |
||||
import { CompilerStatus } from "../types" |
||||
|
||||
export function SetupExportsBtn ({ handleRunSetup, status }: { handleRunSetup: () => Promise<void>, status: CompilerStatus }) { |
||||
return <button |
||||
className="btn btn-secondary btn-block d-block w-100 text-break mt-2" |
||||
onClick={handleRunSetup} |
||||
data-id="runSetupBtn" |
||||
> |
||||
<CustomTooltip |
||||
placement="auto" |
||||
tooltipId="overlay-tooltip-compile" |
||||
tooltipText={ |
||||
<div className="text-left"> |
||||
<div> |
||||
Click to setup and export verification keys |
||||
</div> |
||||
</div> |
||||
} |
||||
> |
||||
<div className="d-flex align-items-center justify-content-center"> |
||||
<RenderIf condition={status === 'exporting'}> |
||||
<i className="fas fa-sync fa-spin mr-2" aria-hidden="true"></i> |
||||
</RenderIf> |
||||
<div className="text-truncate overflow-hidden text-nowrap"> |
||||
<span> |
||||
<FormattedMessage id="circuit.runSetup" /> |
||||
</span> |
||||
</div> |
||||
</div> |
||||
</CustomTooltip> |
||||
</button> |
||||
} |
@ -0,0 +1,50 @@ |
||||
import { useEffect, useState } from "react" |
||||
import { FormattedMessage } from "react-intl" |
||||
import { CustomTooltip, RenderIf, RenderIfNot } from "@remix-ui/helper" |
||||
|
||||
export function Toggler ({ children, title, dataId, show = false, icon, iconTooltip }: { children: JSX.Element, title: string, dataId: string, show?: boolean, icon?: string, iconTooltip?: string }) { |
||||
const [toggleExpander, setToggleExpander] = useState<boolean>(show) |
||||
|
||||
useEffect(() => { |
||||
setToggleExpander(show) |
||||
}, [show]) |
||||
|
||||
const toggleConfigurations = () => { |
||||
setToggleExpander(!toggleExpander) |
||||
} |
||||
|
||||
return ( |
||||
<div className="pt-2 border-top pb-2"> |
||||
<div className="d-flex circuit_config_section justify-content-between" onClick={toggleConfigurations} data-id={dataId}> |
||||
<div className="d-flex"> |
||||
<label className="mt-1 circuit_config_section"> |
||||
<FormattedMessage id={title} /> |
||||
{ icon ? iconTooltip ? ( |
||||
<CustomTooltip |
||||
placement="auto" |
||||
tooltipId="rerunSetupWarningTooltip" |
||||
tooltipClasses="text-nowrap" |
||||
tooltipText={iconTooltip} |
||||
> |
||||
<span className={`${icon} border-0 p-0 ml-2`} aria-hidden="true"></span> |
||||
</CustomTooltip>) : |
||||
<span className={`${icon} border-0 p-0 ml-2`} aria-hidden="true"></span> : null } |
||||
</label> |
||||
</div> |
||||
<div> |
||||
<span data-id="scConfigExpander" onClick={toggleConfigurations}> |
||||
<RenderIf condition={toggleExpander}> |
||||
<i className="fas fa-angle-down" aria-hidden="true"></i> |
||||
</RenderIf> |
||||
<RenderIfNot condition={toggleExpander}> |
||||
<i className="fas fa-angle-right" aria-hidden="true"></i> |
||||
</RenderIfNot> |
||||
</span> |
||||
</div> |
||||
</div> |
||||
<RenderIf condition={toggleExpander}> |
||||
{ children } |
||||
</RenderIf> |
||||
</div> |
||||
) |
||||
} |
@ -1,36 +0,0 @@ |
||||
import { useState } from "react" |
||||
import { FormattedMessage } from "react-intl" |
||||
import { RenderIf, RenderIfNot } from "@remix-ui/helper" |
||||
|
||||
export function WitnessToggler ({ children }: { children: JSX.Element }) { |
||||
const [toggleExpander, setToggleExpander] = useState<boolean>(false) |
||||
|
||||
const toggleConfigurations = () => { |
||||
setToggleExpander(!toggleExpander) |
||||
} |
||||
|
||||
return ( |
||||
<div> |
||||
<div className="d-flex circuit_config_section justify-content-between" onClick={toggleConfigurations} data-id="witness_toggler"> |
||||
<div className="d-flex"> |
||||
<label className="mt-1 circuit_config_section"> |
||||
<FormattedMessage id="circuit.computeWitness" /> |
||||
</label> |
||||
</div> |
||||
<div> |
||||
<span data-id="scConfigExpander" onClick={toggleConfigurations}> |
||||
<RenderIf condition={toggleExpander}> |
||||
<i className="fas fa-angle-down" aria-hidden="true"></i> |
||||
</RenderIf> |
||||
<RenderIfNot condition={toggleExpander}> |
||||
<i className="fas fa-angle-right" aria-hidden="true"></i> |
||||
</RenderIfNot> |
||||
</span> |
||||
</div> |
||||
</div> |
||||
<RenderIf condition={toggleExpander}> |
||||
{ children } |
||||
</RenderIf> |
||||
</div> |
||||
) |
||||
} |
@ -0,0 +1,34 @@ |
||||
import { NightwatchBrowser } from 'nightwatch' |
||||
import EventEmitter from 'events' |
||||
|
||||
class HideMetaMaskPopup extends EventEmitter { |
||||
command(this: NightwatchBrowser) { |
||||
browser |
||||
.pause(5000) |
||||
.isVisible({ |
||||
selector: 'button[data-testid="popover-close"]', |
||||
locateStrategy: 'css selector', |
||||
suppressNotFoundErrors: true, |
||||
timeout: 2000 |
||||
}, (okVisible) => { |
||||
console.log('okVisible', okVisible) |
||||
if (!okVisible.value) { |
||||
console.log('popover not found') |
||||
} else { |
||||
console.log('popover found... closing') |
||||
browser.click('button[data-testid="popover-close"]') |
||||
} |
||||
}) |
||||
.waitForElementNotPresent({ |
||||
selector: 'button[data-testid="popover-close"]', |
||||
locateStrategy: 'css selector', |
||||
timeout: 2000 |
||||
}) |
||||
.perform((done) => { |
||||
done() |
||||
this.emit('complete') |
||||
}) |
||||
} |
||||
} |
||||
|
||||
module.exports = HideMetaMaskPopup |
@ -0,0 +1,20 @@ |
||||
import { NightwatchBrowser } from 'nightwatch' |
||||
import EventEmitter from 'events' |
||||
|
||||
class pinGrid extends EventEmitter { |
||||
command (this: NightwatchBrowser, provider: string, status: boolean): NightwatchBrowser { |
||||
this.api.useCss().waitForElementVisible('[data-id="settingsSelectEnvOptions"]') |
||||
.click('[data-id="settingsSelectEnvOptions"] button') |
||||
.waitForElementVisible(`[data-id="dropdown-item-another-chain"]`) |
||||
.click(`[data-id="dropdown-item-another-chain"]`) |
||||
.waitForElementVisible(`[data-id="${provider}-${status ? 'unpinned' : 'pinned'}"]`) |
||||
.click(`[data-id="${provider}-${status ? 'unpinned' : 'pinned'}"]`) |
||||
.perform((done) => { |
||||
done() |
||||
this.emit('complete') |
||||
}) |
||||
return this |
||||
} |
||||
} |
||||
|
||||
module.exports = pinGrid |
@ -1,7 +1,9 @@ |
||||
|
||||
cd /tmp/ |
||||
rm -rf git/bare.git |
||||
rm -rf git/bare2.git |
||||
rm -rf git |
||||
mkdir -p git |
||||
cd git |
||||
git clone --bare https://github.com/ethereum/awesome-remix bare.git |
||||
git clone --bare https://github.com/ethereum/awesome-remix bare2.git |
||||
|
@ -0,0 +1,36 @@ |
||||
'use strict' |
||||
import { NightwatchBrowser } from 'nightwatch' |
||||
import init from '../helpers/init' |
||||
|
||||
module.exports = { |
||||
before: function (browser: NightwatchBrowser, done: VoidFunction) { |
||||
init(browser, done, 'http://127.0.0.1:8080?plugins=solidity,udapp', false) |
||||
}, |
||||
'pin chain': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.clickLaunchIcon('udapp') |
||||
.pinGrid('vm-custom-fork', true) |
||||
.waitForElementVisible('[data-id="settingsSelectEnvOptions"]') |
||||
.click('[data-id="settingsSelectEnvOptions"] button') |
||||
.waitForElementVisible(`[data-id="dropdown-item-vm-custom-fork"]`) |
||||
.click('[data-id="settingsSelectEnvOptions"] button') // close the dropdown
|
||||
.pinGrid('vm-sepolia-fork', true) |
||||
.waitForElementVisible('[data-id="settingsSelectEnvOptions"]') |
||||
.click('[data-id="settingsSelectEnvOptions"] button') |
||||
.waitForElementVisible(`[data-id="dropdown-item-vm-sepolia-fork"]`) |
||||
.click('[data-id="settingsSelectEnvOptions"] button') // close the dropdown
|
||||
}, |
||||
'unpin chain': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.pinGrid('vm-custom-fork', false) |
||||
.waitForElementVisible('[data-id="settingsSelectEnvOptions"]') |
||||
.click('[data-id="settingsSelectEnvOptions"] button') |
||||
.waitForElementNotPresent(`[data-id="dropdown-item-vm-custom-fork"]`) |
||||
.click('[data-id="settingsSelectEnvOptions"] button') // close the dropdown
|
||||
.pinGrid('vm-sepolia-fork', false) |
||||
.waitForElementVisible('[data-id="settingsSelectEnvOptions"]') |
||||
.click('[data-id="settingsSelectEnvOptions"] button') |
||||
.waitForElementNotPresent(`[data-id="dropdown-item-vm-sepolia-fork"]`) |
||||
.click('[data-id="settingsSelectEnvOptions"] button') // close the dropdown
|
||||
} |
||||
} |
@ -0,0 +1,43 @@ |
||||
# Simple workflow for deploying static content to GitHub Pages |
||||
name: Deploy static content to Pages |
||||
|
||||
on: |
||||
# Runs on pushes targeting the default branch |
||||
push: |
||||
branches: ["gh-pages"] |
||||
|
||||
# Allows you to run this workflow manually from the Actions tab |
||||
workflow_dispatch: |
||||
|
||||
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages |
||||
permissions: |
||||
contents: read |
||||
pages: write |
||||
id-token: write |
||||
|
||||
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. |
||||
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. |
||||
concurrency: |
||||
group: "pages" |
||||
cancel-in-progress: false |
||||
|
||||
jobs: |
||||
# Single deploy job since we're just deploying |
||||
deploy: |
||||
environment: |
||||
name: github-pages |
||||
url: ${{ steps.deployment.outputs.page_url }} |
||||
runs-on: ubuntu-latest |
||||
steps: |
||||
- name: Checkout |
||||
uses: actions/checkout@v4 |
||||
- name: Setup Pages |
||||
uses: actions/configure-pages@v5 |
||||
- name: Upload artifact |
||||
uses: actions/upload-pages-artifact@v3 |
||||
with: |
||||
# Upload entire repository |
||||
path: '.' |
||||
- name: Deploy to GitHub Pages |
||||
id: deployment |
||||
uses: actions/deploy-pages@v4 |
@ -0,0 +1,12 @@ |
||||
.TSCellStyle { |
||||
min-height: 8.5rem; |
||||
max-width: 13rem; |
||||
min-width: 13rem; |
||||
max-height: 8.5rem; |
||||
} |
||||
|
||||
.badgeForCell { |
||||
max-width: fit-content; |
||||
padding-right: 0.5rem; |
||||
font-size: smaller; |
||||
} |
@ -0,0 +1,269 @@ |
||||
|
||||
import React from 'react' |
||||
import { FormattedMessage, useIntl } from 'react-intl' |
||||
import { CustomTooltip } from "@remix-ui/helper" |
||||
import { AppModal } from '@remix-ui/app' |
||||
import { ViewPlugin } from '@remixproject/engine-web' |
||||
import { PluginViewWrapper } from '@remix-ui/helper' |
||||
import { RemixUIGridView } from '@remix-ui/remix-ui-grid-view' |
||||
import { RemixUIGridSection } from '@remix-ui/remix-ui-grid-section' |
||||
import { RemixUIGridCell } from '@remix-ui/remix-ui-grid-cell' |
||||
import isElectron from 'is-electron' |
||||
import type { TemplateGroup } from '@remix-ui/workspace' |
||||
import './templates-selection-plugin.css' |
||||
import { templates } from './templates' |
||||
|
||||
//@ts-ignore
|
||||
const _paq = (window._paq = window._paq || []) |
||||
|
||||
const profile = { |
||||
name: 'templateSelection', |
||||
displayName: 'Template Selection', |
||||
description: 'templateSelection', |
||||
location: 'mainPanel', |
||||
methods: [], |
||||
events: [], |
||||
maintainedBy: 'Remix', |
||||
} |
||||
|
||||
export class TemplatesSelectionPlugin extends ViewPlugin { |
||||
templates: Array<TemplateGroup> |
||||
dispatch: React.Dispatch<any> = () => { } |
||||
constructor() { |
||||
super(profile) |
||||
} |
||||
|
||||
async onActivation() { |
||||
this.handleThemeChange() |
||||
await this.call('tabs', 'focus', 'templateSelection') |
||||
this.renderComponent() |
||||
_paq.push(['trackEvent', 'plugin', 'activated', 'remixGuide']) |
||||
} |
||||
|
||||
onDeactivation(): void { |
||||
} |
||||
|
||||
private handleThemeChange() { |
||||
this.on('theme', 'themeChanged', (theme: any) => { |
||||
this.renderComponent() |
||||
}) |
||||
} |
||||
|
||||
setDispatch(dispatch: React.Dispatch<any>): void { |
||||
this.dispatch = dispatch |
||||
this.renderComponent() |
||||
} |
||||
|
||||
render() { |
||||
return ( |
||||
<div className="bg-dark" id="remixGuide"> |
||||
<PluginViewWrapper plugin={this} /> |
||||
</div> |
||||
) |
||||
} |
||||
|
||||
renderComponent() { |
||||
this.dispatch({ |
||||
...this, |
||||
}) |
||||
} |
||||
|
||||
updateComponent() { |
||||
/* |
||||
This represents the different options available from the openzeppelin library. |
||||
const opts = { |
||||
// @ts-ignore: Object is possibly 'null'.
|
||||
mintable: mintableCheckboxRef.current.checked, |
||||
// @ts-ignore: Object is possibly 'null'.
|
||||
burnable: burnableCheckboxRef.current.checked, |
||||
// @ts-ignore: Object is possibly 'null'.
|
||||
pausable: pausableCheckboxRef.current.checked, |
||||
// @ts-ignore: Object is possibly 'null'.
|
||||
upgradeable: transparentRadioRef.current.checked ? transparentRadioRef.current.value : uupsRadioRef.current.checked ? uupsRadioRef.current.value : false |
||||
} |
||||
*/ |
||||
const createWorkspace = async (item) => { |
||||
const defaultName = await this.call('filePanel', 'getAvailableWorkspaceName', item.displayName) |
||||
|
||||
const username = await this.call('settings', 'get', 'settings/github-user-name') |
||||
const email = await this.call('settings', 'get', 'settings/github-email') |
||||
const gitNotSet = !username || !email |
||||
let workspaceName = defaultName |
||||
let initGit = false |
||||
const modal: AppModal = { |
||||
id: 'TemplatesSelection', |
||||
title: window._intl.formatMessage({ id: !isElectron() ? 'filePanel.workspace.create': 'filePanel.workspace.create.desktop' }), |
||||
message: await createModalMessage(defaultName, gitNotSet, (value) => workspaceName = value, (value) => initGit = !!value), |
||||
okLabel: window._intl.formatMessage({ id: !isElectron() ? 'filePanel.ok':'filePanel.selectFolder' }), |
||||
} |
||||
const modalResult = await this.call('notification', 'modal', modal) |
||||
if (!modalResult) return |
||||
this.emit('createWorkspaceReducerEvent', workspaceName, item.value, item.opts, false, async (e, data) => { |
||||
if (e) { |
||||
const modal: AppModal = { |
||||
id: 'TemplatesSelection', |
||||
title: window._intl.formatMessage({ id: !isElectron() ? 'filePanel.workspace.create': 'filePanel.workspace.create.desktop' }), |
||||
message: e.message, |
||||
okLabel: window._intl.formatMessage({ id: 'filePanel.ok' }), |
||||
cancelLabel: window._intl.formatMessage({ id: 'filePanel.cancel' }) |
||||
} |
||||
await this.call('notification', 'modal', modal) |
||||
console.error(e) |
||||
} |
||||
|
||||
}, initGit) |
||||
} |
||||
|
||||
const addToCurrentWorkspace = async (item) => { |
||||
this.emit('addTemplateToWorkspaceReducerEvent', item.value, item.opts, false, async (e, data) => { |
||||
if (e) { |
||||
const modal: AppModal = { |
||||
id: 'TemplatesSelection', |
||||
title: window._intl.formatMessage({ id: !isElectron() ? 'filePanel.workspace.create': 'filePanel.workspace.create.desktop' }), |
||||
message: e.message, |
||||
okLabel: window._intl.formatMessage({ id: 'filePanel.ok' }), |
||||
cancelLabel: window._intl.formatMessage({ id: 'filePanel.cancel' }) |
||||
} |
||||
await this.call('notification', 'modal', modal) |
||||
console.error(e) |
||||
} else { |
||||
this.call('notification', 'toast', 'Files Added.') |
||||
} |
||||
}) |
||||
} |
||||
|
||||
return ( |
||||
<RemixUIGridView |
||||
plugin={this} |
||||
styleList={""} |
||||
logo='/assets/img/bgRemi.webp' |
||||
enableFilter={true} |
||||
showUntagged={true} |
||||
showPin={false} |
||||
tagList={[ |
||||
['Solidity', 'danger'], |
||||
['ZKP', 'warning'], |
||||
['ERC20', 'success'], |
||||
['ERC721', 'secondary'], |
||||
['ERC1155', 'primary'], |
||||
]} |
||||
title='Template explorer' |
||||
description="Select a template to create a workspace or to add it to current workspace" |
||||
> |
||||
{ |
||||
templates(window._intl).map(template => { |
||||
return <RemixUIGridSection |
||||
plugin={this} |
||||
key={template.name} |
||||
title={template.name} |
||||
hScrollable={false} |
||||
> |
||||
{template.items.map(item => { |
||||
return <RemixUIGridCell |
||||
plugin={this} |
||||
title={item.displayName} |
||||
key={item.name} |
||||
id={item.name} |
||||
searchKeywords={[item.displayName, item.description, template.name]} |
||||
tagList={item.tagList} |
||||
classList='TSCellStyle' |
||||
> |
||||
<div className='d-flex justify-content-between h-100 flex-column'> |
||||
<div className='d-flex flex-column'> |
||||
<div> |
||||
{item.description && <span className='text-dark'>{item.description}</span>} |
||||
</div> |
||||
<div className='d-flex flex-wrap'> |
||||
{(item.opts && item.opts.upgradeable && item.opts.upgradeable === 'uupds') && <span className='badgeForCell badge text-secondary'>Upgradeable-UUPS</span>} |
||||
{(item.opts && item.opts.mintable) && <span className='badgeForCell text-secondary'>mintable</span>} |
||||
{(item.opts && item.opts.burnable) && <span className='badgeForCell text-secondary'>burnable</span>} |
||||
{(item.opts && item.opts.pausable) && <span className='badgeForCell text-secondary'>pausable</span>} |
||||
</div> |
||||
</div> |
||||
<div className='align-items-center justify-content-between w-100 d-flex pt- flex-row'> |
||||
{(!template.IsArtefact || !item.isArtefact) && <CustomTooltip |
||||
placement="auto" |
||||
tooltipId={`overlay-tooltip-new${item.name}`} |
||||
tooltipText="Create a new workspace" |
||||
> |
||||
<span |
||||
data-id={`create-${item.value}${item.opts ? JSON.stringify(item.opts) : ''}`} |
||||
onClick={async () => createWorkspace(item)} |
||||
className="btn btn-sm mr-2 border border-primary" |
||||
> |
||||
Create |
||||
</span> |
||||
</CustomTooltip>} |
||||
<CustomTooltip |
||||
placement="auto" |
||||
tooltipId={`overlay-tooltip-add${item.name}`} |
||||
tooltipText="Add template files to current workspace" |
||||
> |
||||
<span |
||||
data-id={`add-${item.value}`} |
||||
onClick={async () => addToCurrentWorkspace(item)} |
||||
className="btn btn-sm border" |
||||
> |
||||
Add to current |
||||
</span> |
||||
</CustomTooltip> |
||||
</div> |
||||
</div> |
||||
</RemixUIGridCell> |
||||
})} |
||||
</RemixUIGridSection> |
||||
})} |
||||
</RemixUIGridView> |
||||
) |
||||
} |
||||
} |
||||
|
||||
const createModalMessage = async ( |
||||
defaultName: string, |
||||
gitConfigNotSet: boolean, |
||||
onChangeTemplateName: (name: string) => void, |
||||
onChangeInitGit: (name: string) => void) => { |
||||
|
||||
return ( |
||||
<> |
||||
<label id="wsName" className="form-check-label" style={{ fontWeight: 'bolder' }}> |
||||
<FormattedMessage id="filePanel.workspaceName" /> |
||||
</label> |
||||
<input |
||||
type="text" |
||||
data-id="modalDialogCustomPromptTextCreate" |
||||
defaultValue={defaultName} |
||||
className="form-control" |
||||
onChange={(e) => onChangeTemplateName(e.target.value)} |
||||
onInput={(e) => onChangeTemplateName((e.target as any).value)} |
||||
/> |
||||
<div className="d-flex py-2 align-items-center custom-control custom-checkbox"> |
||||
<input |
||||
id="initGitRepository" |
||||
data-id="initGitRepository" |
||||
className="form-check-input custom-control-input" |
||||
type="checkbox" |
||||
disabled={gitConfigNotSet} |
||||
onChange={(e) => onChangeInitGit(e.target.value)} |
||||
onInput={(e) => onChangeInitGit((e.target as any).value)} |
||||
/> |
||||
<label |
||||
htmlFor="initGitRepository" |
||||
data-id="initGitRepositoryLabel" |
||||
className="m-0 form-check-label custom-control-label udapp_checkboxAlign" |
||||
title={window._intl.formatMessage({ id: 'filePanel.initGitRepoTitle' })} |
||||
> |
||||
<FormattedMessage id="filePanel.initGitRepositoryLabel" /> |
||||
</label> |
||||
</div> |
||||
{gitConfigNotSet ? ( |
||||
<div className="text-warning"> |
||||
<FormattedMessage id="filePanel.initGitRepositoryWarning" /> |
||||
</div> |
||||
) : ( |
||||
<></> |
||||
)} |
||||
</> |
||||
) |
||||
} |
||||
|
@ -0,0 +1,357 @@ |
||||
|
||||
export const templates = (intl) => { |
||||
return [ |
||||
{ |
||||
name: "Generic", |
||||
items: [ |
||||
{ value: "remixDefault", tagList: ["Solidity"], displayName: intl.formatMessage({ id: 'filePanel.basic' }), description: 'A default project' }, |
||||
{ value: "blank", displayName: intl.formatMessage({ id: 'filePanel.blank' }), IsArtefact: true, description: 'A blank project' } |
||||
] |
||||
}, |
||||
{ |
||||
name: "OpenZeppelin", |
||||
items: [ |
||||
{ |
||||
value: "ozerc20", |
||||
displayName: "ERC20", |
||||
tagList: ["ERC20", "Solidity"], |
||||
description: 'A simple ERC20 project' |
||||
}, |
||||
{ |
||||
value: "ozerc721", |
||||
displayName: "ERC721 (NFT)", |
||||
tagList: ["ERC721", "Solidity"], |
||||
description: 'A simple ERC721 (aka NFT) project' |
||||
}, |
||||
{ |
||||
value: "ozerc1155", |
||||
tagList: ["Solidity"], |
||||
displayName: "ERC1155", |
||||
description: 'A simple ERC1155 (multi token) project' |
||||
}, |
||||
{ |
||||
value: "ozerc20", |
||||
displayName: "ERC20", |
||||
description: "A standard interface for fungible tokens", |
||||
tagList: ["Solidity"], |
||||
opts: { |
||||
mintable: true |
||||
} |
||||
}, |
||||
{ |
||||
value: "ozerc721", |
||||
displayName: "ERC721 (NFT)", |
||||
description: "Non-fungible Token Standard", |
||||
tagList: ["Solidity", "ERC721"], |
||||
opts: { |
||||
mintable: true |
||||
} |
||||
}, |
||||
{ |
||||
value: "ozerc1155", |
||||
displayName: "ERC1155", |
||||
tagList: ["Solidity"], |
||||
description: "A standard interface for contracts that manage multiple token types", |
||||
opts: { |
||||
mintable: true |
||||
} |
||||
}, |
||||
{ |
||||
value: "ozerc20", |
||||
displayName: "ERC20", |
||||
description: "A standard interface for fungible tokens", |
||||
tagList: ["Solidity", "ERC20"], |
||||
opts: { |
||||
mintable: true, |
||||
burnable: true |
||||
}, |
||||
}, |
||||
{ |
||||
value: "ozerc721", |
||||
displayName: "ERC721 (NFT)", |
||||
description: "Non-fungible Token Standard", |
||||
opts: { |
||||
mintable: true, |
||||
burnable: true |
||||
}, |
||||
tagList: ["ERC721", "Solidity"] |
||||
}, |
||||
{ |
||||
value: "ozerc1155", |
||||
displayName: "ERC1155", |
||||
description: "A standard interface for contracts that manage multiple token types", |
||||
opts: { |
||||
mintable: true, |
||||
burnable: true |
||||
}, |
||||
tagList: ["ERC1155", "Solidity"] |
||||
}, |
||||
{ |
||||
value: "ozerc20", |
||||
displayName: "ERC20", |
||||
description: "A standard interface for fungible tokens", |
||||
opts: { |
||||
mintable: true, |
||||
pausable: true |
||||
}, |
||||
tagList: ["ERC20", "Solidity"] |
||||
}, |
||||
{ |
||||
value: "ozerc721", |
||||
displayName: "ERC721 (NFT)", |
||||
description: "Non-fungible Token Standard", |
||||
opts: { |
||||
mintable: true, |
||||
pausable: true |
||||
}, |
||||
tagList: ["ERC721", "Solidity"] |
||||
}, |
||||
{ |
||||
value: "ozerc1155", |
||||
displayName: "ERC1155", |
||||
description: "A standard interface for contracts that manage multiple token types", |
||||
tagList: ["ERC20"], |
||||
opts: { |
||||
mintable: true, |
||||
pausable: true |
||||
} |
||||
} |
||||
] |
||||
}, |
||||
{ |
||||
name: "OpenZeppelin Proxy", |
||||
items: [ |
||||
{ |
||||
value: "ozerc20", |
||||
displayName: "ERC20", |
||||
description: "A standard interface for fungible tokens", |
||||
opts: { |
||||
upgradeable: 'uups' |
||||
}, |
||||
tagList: ["ERC20", "Solidity"] |
||||
}, |
||||
{ |
||||
value: "ozerc721", |
||||
displayName: "ERC721 (NFT)", |
||||
description: "Non-fungible Token Standard", |
||||
opts: { |
||||
upgradeable: 'uups' |
||||
}, |
||||
tagList: ["ERC721", "Solidity"] |
||||
}, |
||||
{ |
||||
value: "ozerc1155", |
||||
displayName: "ERC1155", |
||||
description: "A standard interface for contracts that manage multiple token types", |
||||
opts: { |
||||
upgradeable: 'uups' |
||||
}, |
||||
tagList: ["ERC1155", "Solidity"] |
||||
}, |
||||
{ |
||||
value: "ozerc20", |
||||
displayName: "ERC20", |
||||
description: "A standard interface for fungible tokens", |
||||
opts: { |
||||
upgradeable: 'uups', |
||||
mintable: true |
||||
}, |
||||
tagList: ["ERC20", "Solidity"] |
||||
}, |
||||
{ |
||||
value: "ozerc721", |
||||
displayName: "ERC721 (NFT)", |
||||
description: "Non-fungible Token Standard", |
||||
opts: { |
||||
upgradeable: 'uups', |
||||
mintable: true |
||||
}, |
||||
tagList: ["ERC721", "Solidity"] |
||||
}, |
||||
{ |
||||
value: "ozerc1155", |
||||
displayName: "ERC1155", |
||||
description: "A standard interface for contracts that manage multiple token types", |
||||
opts: { |
||||
upgradeable: 'uups', |
||||
mintable: true |
||||
}, |
||||
tagList: ["ERC1155", "Solidity"] |
||||
}, |
||||
{ |
||||
value: "ozerc20", |
||||
displayName: "ERC20", |
||||
description: "A standard interface for fungible tokens", |
||||
opts: { |
||||
upgradeable: 'uups', |
||||
mintable: true, |
||||
burnable: true |
||||
}, |
||||
tagList: ["ERC20", "Solidity"] |
||||
}, |
||||
{ |
||||
value: "ozerc721", |
||||
displayName: "ERC721 (NFT)", |
||||
description: "Non-fungible Token Standard", |
||||
opts: { |
||||
upgradeable: 'uups', |
||||
mintable: true, |
||||
burnable: true |
||||
}, |
||||
tagList: ["ERC721", "Solidity"] |
||||
}, |
||||
{ |
||||
value: "ozerc1155", |
||||
displayName: "ERC1155", |
||||
description: "A standard interface for contracts that manage multiple token types", |
||||
opts: { |
||||
upgradeable: 'uups', |
||||
mintable: true, |
||||
burnable: true |
||||
}, |
||||
tagList: ["ERC1155", "Solidity"] |
||||
}, |
||||
{ |
||||
value: "ozerc20", |
||||
displayName: "ERC20", |
||||
description: "A standard interface for fungible tokens", |
||||
opts: { |
||||
upgradeable: 'uups', |
||||
mintable: true, |
||||
pausable: true |
||||
}, |
||||
tagList: ["ERC20", "Solidity"] |
||||
}, |
||||
{ |
||||
value: "ozerc721", |
||||
displayName: "ERC721 (NFT)", |
||||
description: "Non-fungible Token Standard", |
||||
opts: { |
||||
upgradeable: 'uups', |
||||
mintable: true, |
||||
pausable: true |
||||
}, |
||||
tagList: ["ERC721", "Solidity"] |
||||
}, |
||||
{ |
||||
value: "ozerc1155", |
||||
displayName: "ERC1155", |
||||
description: "A standard interface for contracts that manage multiple token types", |
||||
opts: { |
||||
upgradeable: 'uups', |
||||
mintable: true, |
||||
pausable: true |
||||
}, |
||||
tagList: ["ERC1155", "Solidity"] |
||||
}, |
||||
{ |
||||
value: "ozerc1155", |
||||
displayName: "ERC1155", |
||||
description: "A standard interface for contracts that manage multiple token types", |
||||
opts: { |
||||
upgradeable: 'uups', |
||||
mintable: true, |
||||
burnable: true, |
||||
pausable: true |
||||
}, |
||||
tagList: ["ERC1155", "Solidity"] |
||||
} |
||||
] |
||||
}, |
||||
{ |
||||
name: "OxProject", |
||||
items: [ |
||||
{ value: "zeroxErc20", displayName: "ERC20", tagList: ["ERC20", "Solidity"], description: "A standard interface for fungible tokens by 0xProject" } |
||||
] |
||||
}, |
||||
{ |
||||
name: "Gnosis Safe", |
||||
items: [ |
||||
{ value: "gnosisSafeMultisig", tagList: ["Solidity"], displayName: intl.formatMessage({ id: 'filePanel.multiSigWallet' }), description: 'Deploy or Customize the Gnosis Safe.' } |
||||
] |
||||
}, |
||||
{ |
||||
name: "Circom ZKP", |
||||
items: [ |
||||
{ value: "semaphore", tagList: ["ZKP"], displayName: intl.formatMessage({ id: 'filePanel.semaphore' }), description: 'Run a ZK Semaphore circom circuit.' }, |
||||
{ value: "hashchecker", tagList: ["ZKP"], displayName: intl.formatMessage({ id: 'filePanel.hashchecker' }), description: 'Run a ZK Hash checker circom circuit.' }, |
||||
{ value: "rln", tagList: ["ZKP"], displayName: intl.formatMessage({ id: 'filePanel.rln' }), description: 'Run a Rate Limiting Nullifier circom circuit.' } |
||||
] |
||||
}, |
||||
{ |
||||
name: "Generic ZKP", |
||||
items: [ |
||||
{ |
||||
value: "sindriScripts", |
||||
tagList: ["ZKP"], |
||||
displayName: intl.formatMessage({ id: 'filePanel.addscriptsindri' }), |
||||
description: 'Use the Sindri API to compile and generate proof.' |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
name: "Uniswap V4", |
||||
items: [ |
||||
{ value: "uniswapV4Template", |
||||
displayName: intl.formatMessage({ id: 'filePanel.uniswapV4Template' }), |
||||
description: 'Use an Uniswap hook' |
||||
}, |
||||
{ |
||||
value: "breakthroughLabsUniswapv4Hooks", |
||||
displayName: intl.formatMessage({ id: 'filePanel.breakthroughLabsUniswapv4Hooks' }), |
||||
description: 'Use an Uniswap hook developed by Breakthrough Labs' |
||||
}, |
||||
{ |
||||
value: "uniswapV4HookBookMultiSigSwapHook", |
||||
displayName: intl.formatMessage({ id: 'filePanel.uniswapV4HookBookMultiSigSwapHook' }), |
||||
description: 'Use a MultiSigSwapHook developed by Breakthrough Labs' |
||||
} |
||||
] |
||||
}, |
||||
{ |
||||
name: "Solidity CREATE2", |
||||
items: [ |
||||
{ |
||||
value: "contractCreate2Factory", |
||||
tagList: ["Solidity"], |
||||
displayName: intl.formatMessage({ id: 'filePanel.addcreate2solidityfactory' }), |
||||
description: 'Factory for deploying a Contract using the CREATE2 opcode.' |
||||
}, |
||||
{ |
||||
value: "contractDeployerScripts", |
||||
displayName: intl.formatMessage({ id: 'filePanel.addscriptdeployer' }), |
||||
description: 'Script for deploying a Contract using the CREATE2 opcode.' |
||||
} |
||||
] |
||||
}, |
||||
{ |
||||
name: "Contract Verification", |
||||
items: [ |
||||
{ |
||||
value: "etherscanScripts", |
||||
displayName: intl.formatMessage({ id: 'filePanel.addscriptetherscan' }), |
||||
description: 'Script for verifying a Contract in Etherscan.' |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
name: 'Github Actions', |
||||
items: [ |
||||
{ value: "runJsTestAction", |
||||
displayName: intl.formatMessage({ id: 'filePanel.tssoltestghaction' }), |
||||
description: 'A Mocha Chai Test Workflow in a GitHub CI.' |
||||
}, |
||||
{ value: "runSolidityUnittestingAction", |
||||
displayName: intl.formatMessage({ id: 'filePanel.solghaction' }), |
||||
description: 'Run a Solidity Unittest Workflow in a GitHub CI.' |
||||
}, |
||||
{ |
||||
value: "runSlitherAction", |
||||
displayName: intl.formatMessage({ id: 'filePanel.slitherghaction' }), |
||||
description: 'Run a Slither Security Analysis in a GitHub CI.' |
||||
} |
||||
], |
||||
IsArtefact: true |
||||
} |
||||
] |
||||
} |
@ -0,0 +1,207 @@ |
||||
import React from 'react' // eslint-disable-line
|
||||
import { ViewPlugin } from '@remixproject/engine-web' |
||||
import { PluginViewWrapper } from '@remix-ui/helper' |
||||
import { RemixUIGridView } from '@remix-ui/remix-ui-grid-view' |
||||
import { RemixUIGridSection } from '@remix-ui/remix-ui-grid-section' |
||||
import { RemixUIGridCell } from '@remix-ui/remix-ui-grid-cell' |
||||
import './style/environment-explorer.css' |
||||
import type { Provider } from '../../blockchain/blockchain' |
||||
|
||||
import * as packageJson from '../../../../../package.json' |
||||
|
||||
const _paq = (window._paq = window._paq || []) |
||||
|
||||
const profile = { |
||||
name: 'environmentExplorer', |
||||
displayName: 'Environment Explorer', |
||||
icon: 'assets/img/EnvironmentExplorerLogo.webp', |
||||
description: 'Customize the Environments list in Deploy & Run', |
||||
location: 'mainPanel', |
||||
documentation: 'https://remix-ide.readthedocs.io/en/latest/run.html', |
||||
version: packageJson.version, |
||||
maintainedBy: 'Remix', |
||||
permission: true, |
||||
events: [], |
||||
methods: [] |
||||
} |
||||
|
||||
type ProvidersSection = `Injected` | 'Remix VMs' | 'Externals' |
||||
|
||||
export class EnvironmentExplorer extends ViewPlugin { |
||||
providers: { [key in ProvidersSection]: Provider[] } |
||||
providersFlat: { [key: string]: Provider } |
||||
pinnedProviders: string[] |
||||
dispatch: React.Dispatch<any> = () => {} |
||||
|
||||
constructor() { |
||||
super(profile) |
||||
this.providersFlat = {} |
||||
this.providers = { |
||||
'Injected': [], |
||||
'Remix VMs': [], |
||||
'Externals': [] |
||||
} |
||||
} |
||||
|
||||
async onActivation(): Promise<void> { |
||||
this.providersFlat = await this.call('blockchain', 'getAllProviders') |
||||
this.pinnedProviders = await this.call('blockchain', 'getPinnedProviders') |
||||
this.renderComponent() |
||||
} |
||||
|
||||
addProvider (provider: Provider) { |
||||
if (provider.isInjected) { |
||||
this.providers['Injected'].push(provider) |
||||
} else if (provider.isVM) { |
||||
this.providers['Remix VMs'].push(provider) |
||||
} else { |
||||
this.providers['Externals'].push(provider) |
||||
} |
||||
} |
||||
|
||||
setDispatch(dispatch: React.Dispatch<any>): void { |
||||
this.dispatch = dispatch |
||||
this.renderComponent() |
||||
} |
||||
render() { |
||||
return ( |
||||
<div className="bg-dark" id="environmentExplorer"> |
||||
<PluginViewWrapper plugin={this} /> |
||||
</div> |
||||
) |
||||
} |
||||
|
||||
renderComponent() { |
||||
this.dispatch({ |
||||
...this |
||||
}) |
||||
} |
||||
|
||||
updateComponent(state: any) { |
||||
this.providers = { |
||||
'Injected': [], |
||||
'Remix VMs': [], |
||||
'Externals': [] |
||||
} |
||||
for (const [key, provider] of Object.entries(this.providersFlat)) { |
||||
this.addProvider(provider) |
||||
} |
||||
return ( |
||||
<RemixUIGridView |
||||
plugin={this} |
||||
styleList={""} |
||||
logo={profile.icon} |
||||
enableFilter={true} |
||||
showUntagged={true} |
||||
showPin={true} |
||||
title={profile.description} |
||||
description="Select the providers and chains to include them in the ENVIRONMENT select box of the Deploy & Run Transactions plugin." |
||||
> |
||||
<RemixUIGridSection |
||||
plugin={this} |
||||
title='Deploy using a Browser Extension.' |
||||
hScrollable={false} |
||||
> |
||||
{this.providers['Injected'].map(provider => { |
||||
return <RemixUIGridCell |
||||
plugin={this} |
||||
title={provider.displayName} |
||||
logos={provider.logos} |
||||
classList='EECellStyle' |
||||
searchKeywords={['Injected', provider.name, provider.displayName, provider.title, provider.description]} |
||||
pinned={this.pinnedProviders.includes(provider.name)} |
||||
key={provider.name} |
||||
id={provider.name} |
||||
pinStateCallback={async (pinned: boolean) => { |
||||
if (pinned) { |
||||
this.emit('providerPinned', provider.name, provider) |
||||
this.call('notification', 'toast', `"${provider.displayName}" has been added to the Environment list of the Deploy & Run Transactions plugin.`) |
||||
return true |
||||
} |
||||
const providerName = await this.call('blockchain', 'getProvider') |
||||
if (providerName !== provider.name) { |
||||
this.emit('providerUnpinned', provider.name, provider) |
||||
this.call('notification', 'toast', `"${provider.displayName}" has been removed from the Environment list of the Deploy & Run Transactions plugin.`) |
||||
return true |
||||
} else { |
||||
this.call('notification', 'toast', 'Cannot unpin the current selected provider') |
||||
return false |
||||
} |
||||
}} |
||||
> |
||||
<div>{provider.description}</div> |
||||
</RemixUIGridCell> |
||||
})} |
||||
</RemixUIGridSection> |
||||
<RemixUIGridSection |
||||
plugin={this} |
||||
title='Deploy to an In-browser Virtual Machine.' |
||||
hScrollable={false} |
||||
>{this.providers['Remix VMs'].map(provider => { |
||||
return <RemixUIGridCell |
||||
plugin={this} |
||||
title={provider.displayName} |
||||
logos={provider.logos} |
||||
classList='EECellStyle' |
||||
searchKeywords={['Remix VMs', provider.name, provider.displayName, provider.title, provider.description]} |
||||
pinned={this.pinnedProviders.includes(provider.name)} |
||||
key={provider.name} |
||||
id={provider.name} |
||||
pinStateCallback={async (pinned: boolean) => { |
||||
if (pinned) { |
||||
this.emit('providerPinned', provider.name, provider) |
||||
this.call('notification', 'toast', `"${provider.displayName}" has been added to the Environment list of the Deploy & Run Transactions plugin.`) |
||||
return true |
||||
} |
||||
const providerName = await this.call('blockchain', 'getProvider') |
||||
if (providerName !== provider.name) { |
||||
this.emit('providerUnpinned', provider.name, provider) |
||||
this.call('notification', 'toast', `"${provider.displayName}" has been removed from the Environment list of the Deploy & Run Transactions plugin.`) |
||||
return true |
||||
} else { |
||||
this.call('notification', 'toast', 'Cannot unpin the current selected provider') |
||||
return false |
||||
} |
||||
}} |
||||
> |
||||
<div>{provider.description}</div> |
||||
</RemixUIGridCell> |
||||
})}</RemixUIGridSection> |
||||
<RemixUIGridSection |
||||
plugin={this} |
||||
title='Deploy to an external Provider.' |
||||
hScrollable={false} |
||||
>{this.providers['Externals'].map(provider => { |
||||
return <RemixUIGridCell |
||||
plugin={this} |
||||
title={provider.displayName} |
||||
logos={provider.logos} |
||||
classList='EECellStyle' |
||||
searchKeywords={['Externals', provider.name, provider.displayName, provider.title, provider.description]} |
||||
pinned={this.pinnedProviders.includes(provider.name)} |
||||
key={provider.name} |
||||
id={provider.name} |
||||
pinStateCallback={async (pinned: boolean) => { |
||||
if (pinned) { |
||||
this.emit('providerPinned', provider.name, provider) |
||||
this.call('notification', 'toast', `"${provider.displayName}" has been added to the Environment list of the Deploy & Run Transactions plugin.`) |
||||
return true |
||||
} |
||||
const providerName = await this.call('blockchain', 'getProvider') |
||||
if (providerName !== provider.name) { |
||||
this.emit('providerUnpinned', provider.name, provider) |
||||
this.call('notification', 'toast', `"${provider.displayName}" has been removed from the Environment list of the Deploy & Run Transactions plugin.`) |
||||
return true |
||||
} else { |
||||
this.call('notification', 'toast', 'Cannot unpin the current selected provider') |
||||
return false |
||||
} |
||||
}} |
||||
> |
||||
<div>{provider.description}</div> |
||||
</RemixUIGridCell> |
||||
})}</RemixUIGridSection> |
||||
</RemixUIGridView> |
||||
) |
||||
} |
||||
} |
@ -0,0 +1,5 @@ |
||||
.EECellStyle { |
||||
min-height: 6rem; |
||||
max-width: 12rem; |
||||
min-width: 10rem; |
||||
} |
After Width: | Height: | Size: 9.9 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 127 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 6.2 KiB |
After Width: | Height: | Size: 29 KiB |
After Width: | Height: | Size: 72 KiB |
After Width: | Height: | Size: 78 KiB |