Merge remote-tracking branch 'origin/master' into github_fe

largegittest2
yann300 3 months ago
commit c404df23fa
  1. 2
      .github/workflows/pr-reminder.yml
  2. 962
      apps/circuit-compiler/src/app/actions/constant.ts
  3. 134
      apps/circuit-compiler/src/app/actions/index.ts
  4. 18
      apps/circuit-compiler/src/app/app.tsx
  5. 6
      apps/circuit-compiler/src/app/components/actions.tsx
  6. 7
      apps/circuit-compiler/src/app/components/compileBtn.tsx
  7. 36
      apps/circuit-compiler/src/app/components/configToggler.tsx
  8. 4
      apps/circuit-compiler/src/app/components/configurations.tsx
  9. 52
      apps/circuit-compiler/src/app/components/container.tsx
  10. 86
      apps/circuit-compiler/src/app/components/feedback.tsx
  11. 39
      apps/circuit-compiler/src/app/components/generateProof.tsx
  12. 2
      apps/circuit-compiler/src/app/components/options.tsx
  13. 44
      apps/circuit-compiler/src/app/components/r1csBtn.tsx
  14. 110
      apps/circuit-compiler/src/app/components/setupExports.tsx
  15. 34
      apps/circuit-compiler/src/app/components/setupExportsBtn.tsx
  16. 50
      apps/circuit-compiler/src/app/components/toggler.tsx
  17. 57
      apps/circuit-compiler/src/app/components/witness.tsx
  18. 36
      apps/circuit-compiler/src/app/components/witnessToggler.tsx
  19. 83
      apps/circuit-compiler/src/app/reducers/state.ts
  20. 27
      apps/circuit-compiler/src/app/services/circomPluginClient.ts
  21. 43
      apps/circuit-compiler/src/app/types/index.ts
  22. 2
      apps/circuit-compiler/src/css/app.css
  23. 2
      apps/circuit-compiler/src/profile.json
  24. 1
      apps/doc-gen/tsconfig.json
  25. 4
      apps/etherscan/src/app/utils/networks.ts
  26. 8
      apps/etherscan/src/app/views/CaptureKeyView.tsx
  27. 2
      apps/etherscan/src/app/views/VerifyView.tsx
  28. 8
      apps/learneth/src/components/BackButton/index.tsx
  29. 90
      apps/learneth/src/pages/StepDetail/index.tsx
  30. 5
      apps/learneth/src/redux/models/remixide.ts
  31. 5
      apps/learneth/src/redux/models/workshop.ts
  32. 2
      apps/learneth/src/remix-client.ts
  33. 34
      apps/remix-ide-e2e/src/commands/hideMetaMaskPopup.ts
  34. 20
      apps/remix-ide-e2e/src/commands/pinGrid.ts
  35. 2
      apps/remix-ide-e2e/src/commands/refreshPage.ts
  36. 5
      apps/remix-ide-e2e/src/commands/selectFiles.ts
  37. 12
      apps/remix-ide-e2e/src/commands/setupMetamask.ts
  38. 64
      apps/remix-ide-e2e/src/commands/verifyLoad.ts
  39. 2
      apps/remix-ide-e2e/src/githttpbackend/setup.sh
  40. 76
      apps/remix-ide-e2e/src/helpers/init.ts
  41. 10
      apps/remix-ide-e2e/src/tests/ballot.test.ts
  42. 74
      apps/remix-ide-e2e/src/tests/circom.test.ts
  43. 23
      apps/remix-ide-e2e/src/tests/dgit_github.test.ts
  44. 108
      apps/remix-ide-e2e/src/tests/dgit_local.test.ts
  45. 12
      apps/remix-ide-e2e/src/tests/erc721.test.ts
  46. 5
      apps/remix-ide-e2e/src/tests/file_explorer_context_menu.test.ts
  47. 77
      apps/remix-ide-e2e/src/tests/file_explorer_multiselect.test.ts
  48. 36
      apps/remix-ide-e2e/src/tests/grid.test.ts
  49. 55
      apps/remix-ide-e2e/src/tests/runAndDeploy_injected.test.ts
  50. 3
      apps/remix-ide-e2e/src/tests/solidityImport.test.ts
  51. 11
      apps/remix-ide-e2e/src/tests/solidityUnittests.test.ts
  52. 6
      apps/remix-ide-e2e/src/tests/staticAnalysis.test.ts
  53. 1
      apps/remix-ide-e2e/src/tests/terminal.test.ts
  54. 13
      apps/remix-ide-e2e/src/tests/url.test.ts
  55. 132
      apps/remix-ide-e2e/src/tests/workspace.test.ts
  56. 55
      apps/remix-ide-e2e/src/tests/workspace_git.test.ts
  57. 2
      apps/remix-ide-e2e/src/types/index.d.ts
  58. 5
      apps/remix-ide/ci/deploy_from_travis_remix-alpha.sh
  59. 5
      apps/remix-ide/ci/deploy_from_travis_remix-beta.sh
  60. 5
      apps/remix-ide/ci/deploy_from_travis_remix-live.sh
  61. 43
      apps/remix-ide/ci/gh-actions-deploy.yml
  62. 13
      apps/remix-ide/src/app.js
  63. 2
      apps/remix-ide/src/app/editor/editor.js
  64. 14
      apps/remix-ide/src/app/files/fileManager.ts
  65. 2
      apps/remix-ide/src/app/panels/file-panel.js
  66. 13
      apps/remix-ide/src/app/panels/layout.ts
  67. 5
      apps/remix-ide/src/app/plugins/git.tsx
  68. 2
      apps/remix-ide/src/app/plugins/matomo.ts
  69. 2
      apps/remix-ide/src/app/plugins/remixGuide.tsx
  70. 1
      apps/remix-ide/src/app/plugins/remixd-handle.tsx
  71. 12
      apps/remix-ide/src/app/plugins/templates-selection/templates-selection-plugin.css
  72. 269
      apps/remix-ide/src/app/plugins/templates-selection/templates-selection-plugin.tsx
  73. 357
      apps/remix-ide/src/app/plugins/templates-selection/templates.ts
  74. 207
      apps/remix-ide/src/app/providers/environment-explorer.tsx
  75. 5
      apps/remix-ide/src/app/providers/style/environment-explorer.css
  76. 12
      apps/remix-ide/src/app/tabs/locales/en/circuit.json
  77. 2
      apps/remix-ide/src/app/tabs/locales/en/home.json
  78. 1
      apps/remix-ide/src/app/tabs/locales/en/terminal.json
  79. 1
      apps/remix-ide/src/app/tabs/locales/es/terminal.json
  80. 1
      apps/remix-ide/src/app/tabs/locales/fr/terminal.json
  81. 1
      apps/remix-ide/src/app/tabs/locales/ko/terminal.json
  82. 1
      apps/remix-ide/src/app/tabs/locales/ru/terminal.json
  83. 1
      apps/remix-ide/src/app/tabs/locales/zh/terminal.json
  84. 48
      apps/remix-ide/src/app/udapp/run-tab.js
  85. 36
      apps/remix-ide/src/assets/css/themes/remix-candy_ikhg4m.css
  86. 2
      apps/remix-ide/src/assets/css/themes/remix-midcentury_hrzph3.css
  87. BIN
      apps/remix-ide/src/assets/img/EnvironmentExplorerLogo.webp
  88. BIN
      apps/remix-ide/src/assets/img/Walletconnect-logo.png
  89. BIN
      apps/remix-ide/src/assets/img/arbitrum-arb-logo.png
  90. BIN
      apps/remix-ide/src/assets/img/brave.png
  91. BIN
      apps/remix-ide/src/assets/img/foundry.png
  92. BIN
      apps/remix-ide/src/assets/img/hardhat.png
  93. BIN
      apps/remix-ide/src/assets/img/metamask.png
  94. BIN
      apps/remix-ide/src/assets/img/optimism-ethereum-op-logo.png
  95. BIN
      apps/remix-ide/src/assets/img/trust-wallet.png
  96. 55
      apps/remix-ide/src/blockchain/blockchain.tsx
  97. 5
      apps/remix-ide/src/blockchain/execution-context.js
  98. 5
      apps/remix-ide/src/lib/helper.js
  99. 22
      apps/remix-ide/src/remixAppManager.js
  100. 2
      apps/remix-ide/src/remixEngine.js
  101. Some files were not shown because too many files have changed in this diff Show More

@ -14,4 +14,4 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
freeze-date: '2024-07-15T18:00:00Z'
freeze-date: '2024-09-09T18:00:00Z'

@ -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,5 +1,9 @@
import * as snarkjs from 'snarkjs'
import type { CircomPluginClient } from "../services/circomPluginClient"
import { Actions, AppState } from "../types"
import { ActionPayloadTypes, AppState, ICircuitAppContext } from "../types"
import { GROTH16_VERIFIER, PLONK_VERIFIER } from './constant'
import { extractNameFromKey, extractParentFromKey } from '@remix-ui/helper'
import { ethers } from 'ethers'
export const compileCircuit = async (plugin: CircomPluginClient, appState: AppState) => {
try {
@ -9,24 +13,12 @@ export const compileCircuit = async (plugin: CircomPluginClient, appState: AppSt
console.log('Existing circuit compilation in progress')
}
} catch (e) {
plugin.emit('statusChanged', { key: 'error', title: e.message, type: 'error' })
plugin.internalEvents.emit('circuit_compiling_errored', e)
console.error(e)
}
}
export const generateR1cs = async (plugin: CircomPluginClient, appState: AppState) => {
try {
if (appState.status !== "generating") {
await plugin.generateR1cs(appState.filePath, { version: appState.version, prime: appState.primeValue })
} else {
console.log('Existing r1cs generation in progress')
}
} catch (e) {
plugin.internalEvents.emit('circuit_generating_r1cs_errored', e)
console.error('Generating R1CS failed: ', e)
}
}
export const computeWitness = async (plugin: CircomPluginClient, status: string, witnessValues: Record<string, string>) => {
try {
if (status !== "computing") {
@ -37,7 +29,121 @@ export const computeWitness = async (plugin: CircomPluginClient, status: string,
console.log('Existing witness computation in progress')
}
} catch (e) {
plugin.emit('statusChanged', { key: 'error', title: e.message, type: 'error' })
plugin.internalEvents.emit('circuit_computing_witness_errored', e)
console.error('Computing witness failed: ', e)
}
}
export const runSetupAndExport = async (plugin: CircomPluginClient, appState: AppState, dispatch: ICircuitAppContext['dispatch']) => {
try {
dispatch({ type: 'SET_COMPILER_STATUS', payload: 'exporting' })
const ptau_final = `https://ipfs-cluster.ethdevops.io/ipfs/${appState.ptauList.find(ptau => ptau.name === appState.ptauValue)?.ipfsHash}`
await plugin.generateR1cs(appState.filePath, { version: appState.version, prime: appState.primeValue })
const fileName = extractNameFromKey(appState.filePath)
const readPath = extractParentFromKey(appState.filePath) + `/.bin/${fileName.replace('.circom', '.r1cs')}`
// @ts-ignore
const r1csBuffer = await plugin.call('fileManager', 'readFile', readPath, { encoding: null })
// @ts-ignore
const r1cs = new Uint8Array(r1csBuffer)
const zkey_final = { type: "mem" }
if (appState.provingScheme === 'groth16') {
await snarkjs.zKey.newZKey(r1cs, ptau_final, zkey_final, zkLogger(plugin, dispatch, 'SET_SETUP_EXPORT_FEEDBACK'))
const vKey = await snarkjs.zKey.exportVerificationKey(zkey_final, zkLogger(plugin, dispatch, 'SET_SETUP_EXPORT_FEEDBACK'))
if (appState.exportVerificationKey) {
await plugin.call('fileManager', 'writeFile', `${extractParentFromKey(appState.filePath)}/groth16/zk/keys/verification_key.json`, JSON.stringify(vKey, null, 2))
}
if (appState.exportVerificationContract) {
const templates = { groth16: GROTH16_VERIFIER }
const solidityContract = await snarkjs.zKey.exportSolidityVerifier(zkey_final, templates, zkLogger(plugin, dispatch, 'SET_SETUP_EXPORT_FEEDBACK'))
await plugin.call('fileManager', 'writeFile', `${extractParentFromKey(appState.filePath)}/groth16/zk/build/zk_verifier.sol`, solidityContract)
}
dispatch({ type: 'SET_ZKEY', payload: zkey_final })
dispatch({ type: 'SET_VERIFICATION_KEY', payload: vKey })
} else if (appState.provingScheme === 'plonk') {
await snarkjs.plonk.setup(r1cs, ptau_final, zkey_final, zkLogger(plugin, dispatch, 'SET_SETUP_EXPORT_FEEDBACK'))
const vKey = await snarkjs.zKey.exportVerificationKey(zkey_final, zkLogger(plugin, dispatch, 'SET_SETUP_EXPORT_FEEDBACK'))
if (appState.exportVerificationKey) {
await plugin.call('fileManager', 'writeFile', `${extractParentFromKey(appState.filePath)}/plonk/zk/keys/verification_key.json`, JSON.stringify(vKey, null, 2))
}
if (appState.exportVerificationContract) {
const templates = { plonk: PLONK_VERIFIER }
const solidityContract = await snarkjs.zKey.exportSolidityVerifier(zkey_final, templates, zkLogger(plugin, dispatch, 'SET_SETUP_EXPORT_FEEDBACK'))
await plugin.call('fileManager', 'writeFile', `${extractParentFromKey(appState.filePath)}/plonk/zk/build/zk_verifier.sol`, solidityContract)
}
dispatch({ type: 'SET_ZKEY', payload: zkey_final })
dispatch({ type: 'SET_VERIFICATION_KEY', payload: vKey })
}
dispatch({ type: 'SET_COMPILER_STATUS', payload: 'idle' })
dispatch({ type: 'SET_SETUP_EXPORT_STATUS', payload: 'done' })
} catch (e) {
dispatch({ type: 'SET_COMPILER_STATUS', payload: 'errored' })
console.error(e)
}
}
export const generateProof = async (plugin: CircomPluginClient, appState: AppState, dispatch: ICircuitAppContext['dispatch']) => {
try {
dispatch({ type: 'SET_COMPILER_STATUS', payload: 'proving' })
const fileName = extractNameFromKey(appState.filePath)
const r1csPath = extractParentFromKey(appState.filePath) + `/.bin/${fileName.replace('.circom', '.r1cs')}`
// @ts-ignore
const r1csBuffer = await plugin.call('fileManager', 'readFile', r1csPath, { encoding: null })
// @ts-ignore
const r1cs = new Uint8Array(r1csBuffer)
const wtnsPath = r1csPath.replace('.r1cs', '.wtn')
// @ts-ignore
const wtnsBuffer = await plugin.call('fileManager', 'readFile', wtnsPath, { encoding: null })
// @ts-ignore
const wtns = new Uint8Array(wtnsBuffer)
const zkey_final = appState.zKey
const vKey = appState.verificationKey
await snarkjs.wtns.check(r1cs, wtns, zkLogger(plugin, dispatch, 'SET_PROOF_FEEDBACK'))
if (appState.provingScheme === 'groth16') {
const { proof, publicSignals } = await snarkjs.groth16.prove(zkey_final, wtns, zkLogger(plugin, dispatch, 'SET_PROOF_FEEDBACK'))
const verified = await snarkjs.groth16.verify(vKey, publicSignals, proof, zkLogger(plugin, dispatch, 'SET_PROOF_FEEDBACK'))
plugin.call('terminal', 'log', { type: 'log', value: 'zk proof validity ' + verified })
if (appState.exportVerifierCalldata) {
const calldata = await snarkjs.groth16.exportSolidityCallData(proof, publicSignals)
plugin.call('fileManager', 'writeFile', `${extractParentFromKey(appState.filePath)}/groth16/zk/build/verifierCalldata.json`, calldata)
}
} else if (appState.provingScheme === 'plonk') {
const { proof, publicSignals } = await snarkjs.plonk.prove(zkey_final, wtns, zkLogger(plugin, dispatch, 'SET_PROOF_FEEDBACK'))
const verified = await snarkjs.plonk.verify(vKey, publicSignals, proof, zkLogger(plugin, dispatch, 'SET_PROOF_FEEDBACK'))
plugin.call('terminal', 'log', { type: 'log', value: 'zk proof validity ' + verified })
if (appState.exportVerifierCalldata) {
const calldata = await snarkjs.plonk.exportSolidityCallData(proof, publicSignals)
plugin.call('fileManager', 'writeFile', `${extractParentFromKey(appState.filePath)}/plonk/zk/build/verifierCalldata.json`, calldata)
}
}
dispatch({ type: 'SET_COMPILER_STATUS', payload: 'idle' })
dispatch({ type: 'SET_PROOF_FEEDBACK', payload: null })
} catch (e) {
dispatch({ type: 'SET_COMPILER_STATUS', payload: 'errored' })
dispatch({ type: 'SET_PROOF_FEEDBACK', payload: e.message })
console.error(e)
}
}
function zkLogger(plugin: CircomPluginClient, dispatch: ICircuitAppContext['dispatch'], dispatchType: keyof ActionPayloadTypes) {
return {
info: (...args) => plugin.call('terminal', 'log', { type: 'log', value: args.join(' ') }),
debug: (...args) => plugin.call('terminal', 'log', { type: 'log', value: args.join(' ') }),
error: (...args) => {
plugin.call('terminal', 'log', { type: 'error', value: args.join(' ') })
dispatch({ type: dispatchType as any, payload: args.join(' ') })
plugin.emit('statusChanged', { key: args.length, title: `You have ${args.length} problem${args.length === 1 ? '' : 's'}`, type: 'error' })
}
}
}

@ -50,16 +50,11 @@ function App() {
})
plugin.internalEvents.on('circuit_compiling_errored', compilerErrored)
// r1cs events
plugin.internalEvents.on('circuit_generating_r1cs_start', () => dispatch({ type: 'SET_COMPILER_STATUS', payload: 'generating' }))
plugin.internalEvents.on('circuit_generating_r1cs_done', () => dispatch({ type: 'SET_COMPILER_STATUS', payload: 'idle' }))
plugin.internalEvents.on('circuit_generating_r1cs_errored', compilerErrored)
// witness events
plugin.internalEvents.on('circuit_computing_witness_start', () => dispatch({ type: 'SET_COMPILER_STATUS', payload: 'computing' }))
plugin.internalEvents.on('circuit_computing_witness_done', () => {
dispatch({ type: 'SET_COMPILER_STATUS', payload: 'idle' })
dispatch({ type: 'SET_COMPILER_FEEDBACK', payload: null })
dispatch({ type: 'SET_COMPUTE_FEEDBACK', payload: null })
})
plugin.internalEvents.on('circuit_computing_witness_errored', compilerErrored)
@ -86,6 +81,7 @@ function App() {
if (appState.autoCompile) await compileCircuit(plugin, appState)
})()
setIsContentChanged(false)
if (appState.setupExportStatus === 'done') dispatch({ type: 'SET_SETUP_EXPORT_STATUS', payload: 'update' })
}
}, [appState.autoCompile, isContentChanged])
@ -103,6 +99,12 @@ function App() {
dispatch({ type: 'SET_SIGNAL_INPUTS', payload: [] })
dispatch({ type: 'SET_COMPILER_STATUS', payload: 'idle' })
dispatch({ type: 'SET_COMPILER_FEEDBACK', payload: null })
dispatch({ type: 'SET_COMPUTE_FEEDBACK', payload: null })
dispatch({ type: 'SET_SETUP_EXPORT_FEEDBACK', payload: null })
dispatch({ type: 'SET_PROOF_FEEDBACK', payload: null })
dispatch({ type: 'SET_SETUP_EXPORT_STATUS', payload: null })
dispatch({ type: 'SET_VERIFICATION_KEY', payload: null })
dispatch({ type: 'SET_ZKEY', payload: null })
}
}, [appState.filePath])
@ -118,9 +120,9 @@ function App() {
try {
const report = JSON.parse(err.message)
dispatch({ type: 'SET_COMPILER_FEEDBACK', payload: report })
dispatch({ type: 'SET_COMPUTE_FEEDBACK', payload: report })
} catch (e) {
dispatch({ type: 'SET_COMPILER_FEEDBACK', payload: err.message })
dispatch({ type: 'SET_COMPUTE_FEEDBACK', payload: err.message })
}
}

@ -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>
)
}

@ -20,18 +20,15 @@ export function CompileBtn () {
}
>
<button
className="btn btn-primary btn-block d-block w-100 text-break mb-1 mt-3"
className="btn btn-primary btn-block d-block w-100 text-break mb-1 mt-1"
onClick={() => { compileCircuit(plugin, appState) }}
disabled={(appState.filePath === "") || (appState.status === "compiling") || (appState.status === "generating")}
disabled={(appState.filePath === "") || (appState.status === "compiling")}
data-id="compile_circuit_btn"
>
<div className="d-flex align-items-center justify-content-center">
<RenderIf condition={appState.status === 'compiling'}>
<i className="fas fa-sync fa-spin mr-2" aria-hidden="true"></i>
</RenderIf>
<RenderIfNot condition={appState.status === 'compiling'}>
<i className="fas fa-sync mr-2" aria-hidden="true"></i>
</RenderIfNot>
<div className="text-truncate overflow-hidden text-nowrap">
<span>
<FormattedMessage id="circuit.compile" />

@ -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>
)
}

@ -4,9 +4,9 @@ import { ConfigurationsProps, PrimeValue } from "../types"
export function Configurations ({primeValue, setPrimeValue, versionValue}: ConfigurationsProps) {
return (
<div className="pb-2 border-bottom flex-column">
<div className="flex-column">
<div className="flex-column d-flex">
<div className="mb-2 ml-0">
<div className="ml-0">
<label className="circuit_inner_label form-check-label" htmlFor="circuitPrimeSelector">
<FormattedMessage id="circuit.prime" />
</label>

@ -1,16 +1,17 @@
import { useContext } from 'react'
import { CustomTooltip, RenderIf } from '@remix-ui/helper'
import {FormattedMessage} from 'react-intl'
import { FormattedMessage } from 'react-intl'
import { CircuitAppContext } from '../contexts'
import { CompileOptions } from './options'
import { VersionList } from './versions'
import { ConfigToggler } from './configToggler'
import { Toggler } from './toggler'
import { Configurations } from './configurations'
import { CircuitActions } from './actions'
import { WitnessToggler } from './witnessToggler'
import { WitnessSection } from './witness'
import { CompilerFeedback } from './feedback'
import { CompilerReport, PrimeValue } from '../types'
import { SetupExports } from './setupExports'
import { GenerateProof } from './generateProof'
export function Container () {
const circuitApp = useContext(CircuitAppContext)
@ -113,17 +114,48 @@ export function Container () {
</CustomTooltip>
<VersionList setVersion={handleVersionSelect} versionList={circuitApp.appState.versionList} currentVersion={circuitApp.appState.version} />
<CompileOptions setCircuitAutoCompile={handleCircuitAutoCompile} setCircuitHideWarnings={handleCircuitHideWarnings} autoCompile={circuitApp.appState.autoCompile} hideWarnings={circuitApp.appState.hideWarnings} />
<ConfigToggler>
<Toggler title='circuit.advancedConfigurations' dataId=''>
<Configurations setPrimeValue={handlePrimeChange} primeValue={circuitApp.appState.primeValue} versionValue={circuitApp.appState.version} />
</ConfigToggler>
</Toggler>
<CircuitActions />
<RenderIf condition={circuitApp.appState.status !== 'compiling'}>
<CompilerFeedback feedback={circuitApp.appState.compilerFeedback} filePathToId={circuitApp.appState.filePathToId} openErrorLocation={handleOpenErrorLocation} hideWarnings={circuitApp.appState.hideWarnings} askGPT={askGPT} />
</RenderIf>
<RenderIf condition={circuitApp.appState.signalInputs.length > 0}>
<Toggler
title='circuit.setupExports'
dataId='setup_exports_toggler'
show={!circuitApp.appState.setupExportStatus}
icon={ circuitApp.appState.setupExportStatus === 'done' ? 'fas fa-check-circle text-success' : circuitApp.appState.setupExportStatus === 'update' ? 'fas fa-exclamation-triangle text-warning' : null }
iconTooltip={ circuitApp.appState.setupExportStatus === 'update' ? 'circom file content changed, please compile and re-run setup to update exported keys.' : null }
>
<>
<SetupExports />
<RenderIf condition={circuitApp.appState.status !== 'exporting'}>
<CompilerFeedback feedback={circuitApp.appState.setupExportFeedback} filePathToId={circuitApp.appState.filePathToId} openErrorLocation={handleOpenErrorLocation} hideWarnings={circuitApp.appState.hideWarnings} askGPT={askGPT} />
</RenderIf>
</>
</Toggler>
</RenderIf>
<RenderIf condition={circuitApp.appState.signalInputs.length > 0}>
<WitnessToggler>
<WitnessSection plugin={circuitApp.plugin} signalInputs={circuitApp.appState.signalInputs} status={circuitApp.appState.status} />
</WitnessToggler>
<Toggler title='circuit.computeWitness' dataId='witness_toggler' show={!!circuitApp.appState.setupExportStatus}>
<>
<WitnessSection plugin={circuitApp.plugin} signalInputs={circuitApp.appState.signalInputs} status={circuitApp.appState.status} />
<RenderIf condition={circuitApp.appState.status !== 'computing'}>
<CompilerFeedback feedback={circuitApp.appState.computeFeedback} filePathToId={circuitApp.appState.filePathToId} openErrorLocation={handleOpenErrorLocation} hideWarnings={circuitApp.appState.hideWarnings} askGPT={askGPT} />
</RenderIf>
</>
</Toggler>
</RenderIf>
<RenderIf condition={(circuitApp.appState.status !== 'compiling') && (circuitApp.appState.status !== 'computing') && (circuitApp.appState.status !== 'generating')}>
<CompilerFeedback feedback={circuitApp.appState.feedback} filePathToId={circuitApp.appState.filePathToId} openErrorLocation={handleOpenErrorLocation} hideWarnings={circuitApp.appState.hideWarnings} askGPT={askGPT} />
<RenderIf condition={circuitApp.appState.signalInputs.length > 0}>
<Toggler title='circuit.generateProof' dataId='generate_proof_toggler' show={!!circuitApp.appState.setupExportStatus}>
<>
<GenerateProof />
<RenderIf condition={circuitApp.appState.status !== 'proving'}>
<CompilerFeedback feedback={circuitApp.appState.proofFeedback} filePathToId={circuitApp.appState.filePathToId} openErrorLocation={handleOpenErrorLocation} hideWarnings={circuitApp.appState.hideWarnings} askGPT={askGPT} />
</RenderIf>
</>
</Toggler>
</RenderIf>
</div>
</div>

@ -1,11 +1,11 @@
import { useState } from 'react'
import { CompilerFeedbackProps, CompilerReport } from '../types'
import { RenderIf } from '@remix-ui/helper'
import {CopyToClipboard} from '@remix-ui/clipboard'
import { CopyToClipboard } from '@remix-ui/clipboard'
import { FeedbackAlert } from './feedbackAlert'
export function CompilerFeedback ({ feedback, filePathToId, hideWarnings, openErrorLocation, askGPT }: CompilerFeedbackProps) {
const [ showException, setShowException ] = useState<boolean>(true)
const [showException, setShowException] = useState<boolean>(true)
const handleCloseException = () => {
setShowException(false)
@ -23,46 +23,50 @@ export function CompilerFeedback ({ feedback, filePathToId, hideWarnings, openEr
return (
<div>
<div className="circuit_errors_box py-4">
<RenderIf condition={ (typeof feedback === "string") && showException }>
<div className="circuit_feedback error alert alert-danger" data-id="circuit_feedback">
<span> <>{ feedback }</> </span>
<div className="close" data-id="renderer" onClick={handleCloseException}>
<i className="fas fa-times"></i>
</div>
<div className="d-flex pt-1 flex-row-reverse">
<span className="ml-3 pt-1 py-1" >
<CopyToClipboard content={feedback} className="p-0 m-0 far fa-copy error" direction={'top'} />
</span>
</div>
</div>
</RenderIf>
<RenderIf condition={ Array.isArray(feedback) }>
<>
{
Array.isArray(feedback) && feedback.map((response, index) => (
<div key={index} onClick={() => handleOpenError(response)}>
<RenderIf condition={response.type === 'Error'}>
<div className={`circuit_feedback ${response.type.toLowerCase()} alert alert-danger`} data-id="circuit_feedback">
<FeedbackAlert
message={response.message + (response.labels[0] ? ": " + response.labels[0].message + ` ${filePathToId[response.labels[0].file_id]}:${response.labels[0].range.start}:${response.labels[0].range.end}` : '')}
askGPT={ () => handleAskGPT(response) } />
</div>
</RenderIf>
<RenderIf condition={(response.type === 'Warning') && !hideWarnings}>
<div className={`circuit_feedback ${response.type.toLowerCase()} alert alert-warning`} data-id="circuit_feedback">
<FeedbackAlert
message={response.message}
askGPT={() => { handleAskGPT(response) }} />
</div>
</RenderIf>
{
(feedback && typeof feedback === 'string') || (Array.isArray(feedback) && feedback.length > 0) ? (
<div className="circuit_errors_box">
<RenderIf condition={ (typeof feedback === "string") && showException }>
<div className="circuit_feedback error alert alert-danger" data-id="circuit_feedback">
<span> <>{ feedback }</> </span>
<div className="close" data-id="renderer" onClick={handleCloseException}>
<i className="fas fa-times"></i>
</div>
)
)
}
</>
</RenderIf>
</div>
<div className="d-flex pt-1 flex-row-reverse">
<span className="ml-3 pt-1 py-1" >
<CopyToClipboard content={feedback} className="p-0 m-0 far fa-copy error" direction={'top'} />
</span>
</div>
</div>
</RenderIf>
<RenderIf condition={ Array.isArray(feedback) }>
<>
{
Array.isArray(feedback) && feedback.map((response, index) => (
<div key={index} onClick={() => handleOpenError(response)}>
<RenderIf condition={response.type === 'Error'}>
<div className={`circuit_feedback ${response.type.toLowerCase()} alert alert-danger`} data-id="circuit_feedback">
<FeedbackAlert
message={response.message + (response.labels[0] ? ": " + response.labels[0].message + ` ${filePathToId[response.labels[0].file_id]}:${response.labels[0].range.start}:${response.labels[0].range.end}` : '')}
askGPT={ () => handleAskGPT(response) } />
</div>
</RenderIf>
<RenderIf condition={(response.type === 'Warning') && !hideWarnings}>
<div className={`circuit_feedback ${response.type.toLowerCase()} alert alert-warning`} data-id="circuit_feedback">
<FeedbackAlert
message={response.message}
askGPT={() => { handleAskGPT(response) }} />
</div>
</RenderIf>
</div>
)
)
}
</>
</RenderIf>
</div>
) : <></>
}
</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>
)
}

@ -4,7 +4,7 @@ import { CompileOptionsProps } from '../types'
export function CompileOptions ({autoCompile, hideWarnings, setCircuitAutoCompile, setCircuitHideWarnings}: CompileOptionsProps) {
return (
<div className='pb-2'>
<div>
<div className="mt-2 custom-control custom-checkbox">
<input
className="custom-control-input"

@ -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>
)
}

@ -34,37 +34,32 @@ export function WitnessSection ({ plugin, signalInputs, status }: {plugin: Circo
}
return (
<div className="pb-2 border-bottom flex-column">
<div className="flex-column d-flex">
<RenderIf condition={signalInputs.length > 0}>
<>
{
signalInputs.map((input, index) => (
<div className="mb-2 ml-0" key={index}>
<label className="circuit_inner_label form-check-label" htmlFor="circuitPrimeSelector">
<FormattedMessage id="circuit.signalInput" /> { input }
</label>
<input className="form-control m-0 txinput" placeholder={input} name={input} onChange={handleSignalInput} data-id={`circuit_input_${input}`} />
</div>
))
}
<button
className="btn btn-sm btn-secondary"
onClick={() => { computeWitness(plugin, status, witnessValues) }}
disabled={(status === "compiling") || (status === "generating") || (status === "computing")}
data-id="compute_witness_btn"
>
<RenderIf condition={status === 'computing'}>
<i className="fas fa-sync fa-spin mr-2" aria-hidden="true"></i>
</RenderIf>
<RenderIfNot condition={status === 'computing'}>
<i className="fas fa-sync mr-2" aria-hidden="true"></i>
</RenderIfNot>
<FormattedMessage id="circuit.compute" />
</button>
</>
</RenderIf>
</div>
<div className="flex-column d-flex">
<RenderIf condition={signalInputs.length > 0}>
<>
{
signalInputs.map((input, index) => (
<div className="mb-2 ml-0" key={index}>
<label className="circuit_inner_label form-check-label" htmlFor="circuitPrimeSelector">
<FormattedMessage id="circuit.signalInput" /> { input }
</label>
<input className="form-control m-0 txinput" placeholder={input} name={input} onChange={handleSignalInput} data-id={`circuit_input_${input}`} />
</div>
))
}
<button
className="btn btn-secondary btn-block d-block w-100 text-break mb-1 mt-1"
onClick={() => { computeWitness(plugin, status, witnessValues) }}
disabled={(status === "compiling") || (status === "computing")}
data-id="compute_witness_btn"
>
<RenderIf condition={status === 'computing'}>
<i className="fas fa-sync fa-spin mr-2" aria-hidden="true"></i>
</RenderIf>
<FormattedMessage id="circuit.compute" />
</button>
</>
</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>
)
}

@ -1,3 +1,4 @@
import { PTAU_LIST } from '../actions/constant'
import { Actions, AppState } from '../types'
import { compiler_list } from 'circom_wasm'
@ -11,7 +12,19 @@ export const appInitialState: AppState = {
autoCompile: false,
hideWarnings: false,
signalInputs: [],
feedback: null
compilerFeedback: null,
computeFeedback: null,
proofFeedback: null,
setupExportFeedback: null,
setupExportStatus: null,
provingScheme: 'groth16',
ptauList: PTAU_LIST,
ptauValue: "final_14.ptau",
exportVerificationContract: true,
exportVerificationKey: true,
exportVerifierCalldata: true,
verificationKey: null,
zKey: null
}
export const appReducer = (state = appInitialState, action: Actions): AppState => {
@ -62,7 +75,25 @@ export const appReducer = (state = appInitialState, action: Actions): AppState =
case 'SET_COMPILER_FEEDBACK':
return {
...state,
feedback: action.payload
compilerFeedback: action.payload
}
case 'SET_COMPUTE_FEEDBACK':
return {
...state,
computeFeedback: action.payload
}
case 'SET_SETUP_EXPORT_FEEDBACK':
return {
...state,
setupExportFeedback: action.payload
}
case 'SET_PROOF_FEEDBACK':
return {
...state,
proofFeedback: action.payload
}
case 'SET_FILE_PATH_TO_ID':
@ -71,6 +102,54 @@ export const appReducer = (state = appInitialState, action: Actions): AppState =
filePathToId: action.payload
}
case 'SET_PROVING_SCHEME':
return {
...state,
provingScheme: action.payload
}
case 'SET_PTAU_VALUE':
return {
...state,
ptauValue: action.payload
}
case 'SET_EXPORT_VERIFICATION_CONTRACT':
return {
...state,
exportVerificationContract: action.payload
}
case 'SET_EXPORT_VERIFICATION_KEY':
return {
...state,
exportVerificationKey: action.payload
}
case 'SET_EXPORT_VERIFIER_CALLDATA':
return {
...state,
exportVerifierCalldata: action.payload
}
case 'SET_SETUP_EXPORT_STATUS':
return {
...state,
setupExportStatus: action.payload
}
case 'SET_VERIFICATION_KEY':
return {
...state,
verificationKey: action.payload
}
case 'SET_ZKEY':
return {
...state,
zKey: action.payload
}
default:
throw new Error()
}

@ -10,8 +10,6 @@ import * as compilerV215 from 'circom_wasm/v2.1.5'
import { extractNameFromKey, extractParentFromKey } from '@remix-ui/helper'
import { CompilationConfig, CompilerReport, PrimeValue, ResolverOutput } from '../types'
// @ts-ignore
const _paq = (window._paq = window._paq || [])
export class CircomPluginClient extends PluginClient {
public internalEvents: EventManager
private _compilationConfig: CompilationConfig = {
@ -22,6 +20,11 @@ export class CircomPluginClient extends PluginClient {
private lastParsedFiles: Record<string, string> = {}
private lastCompiledFile: string = ''
private compiler: typeof compilerV215 & typeof compilerV216 & typeof compilerV217 & typeof compilerV218
private _paq = {
push: (args) => {
this.call('matomo' as any, 'track', args)
}
}
constructor() {
super()
@ -164,7 +167,7 @@ export class CircomPluginClient extends PluginClient {
const circuitErrors = circuitApi.report()
this.logCompilerReport(circuitErrors)
_paq.push(['trackEvent', 'circuit-compiler', 'compile', 'Compilation failed'])
this._paq.push(['trackEvent', 'circuit-compiler', 'compile', 'Compilation failed'])
throw new Error(circuitErrors)
} else {
this.lastCompiledFile = path
@ -184,7 +187,7 @@ export class CircomPluginClient extends PluginClient {
} else {
this.internalEvents.emit('circuit_compiling_done', [])
}
_paq.push(['trackEvent', 'circuit-compiler', 'compile', 'Compilation successful'])
this._paq.push(['trackEvent', 'circuit-compiler', 'compile', 'Compilation successful'])
circuitApi.log().map(log => {
log && this.call('terminal', 'log', { type: 'log', value: log })
})
@ -194,24 +197,15 @@ export class CircomPluginClient extends PluginClient {
}
async generateR1cs (path: string, compilationConfig?: CompilationConfig): Promise<void> {
this.internalEvents.emit('circuit_generating_r1cs_start')
this.emit('statusChanged', { key: 'loading', title: 'Generating...', type: 'info' })
// @ts-ignore
this.call('terminal', 'log', { type: 'log', value: 'Generating R1CS for ' + path })
const [parseErrors, filePathToId] = await this.parse(path)
if (parseErrors && (parseErrors.length > 0)) {
if (parseErrors[0].type === 'Error') {
this.internalEvents.emit('circuit_parsing_errored', parseErrors)
this.logCompilerReport(parseErrors)
return
} else if (parseErrors[0].type === 'Warning') {
this.internalEvents.emit('circuit_parsing_warning', parseErrors)
this.logCompilerReport(parseErrors)
}
} else {
this.internalEvents.emit('circuit_parsing_done', parseErrors, filePathToId)
this.emit('statusChanged', { key: 'succeed', title: 'r1cs generated successfully', type: 'success' })
}
if (compilationConfig) {
const { prime, version } = compilationConfig
@ -226,16 +220,15 @@ export class CircomPluginClient extends PluginClient {
const r1csErrors = r1csApi.report()
this.logCompilerReport(r1csErrors)
_paq.push(['trackEvent', 'circuit-compiler', 'generateR1cs', 'R1CS Generation failed'])
this._paq.push(['trackEvent', 'circuit-compiler', 'generateR1cs', 'R1CS Generation failed'])
throw new Error(r1csErrors)
} else {
this.internalEvents.emit('circuit_generating_r1cs_done')
const fileName = extractNameFromKey(path)
const writePath = extractParentFromKey(path) + "/.bin/" + fileName.replace('circom', 'r1cs')
// @ts-ignore
await this.call('fileManager', 'writeFile', writePath, r1csProgram, true)
_paq.push(['trackEvent', 'circuit-compiler', 'generateR1cs', 'R1CS Generation successful'])
this._paq.push(['trackEvent', 'circuit-compiler', 'generateR1cs', 'R1CS Generation successful'])
r1csApi.log().map(log => {
log && this.call('terminal', 'log', { type: 'log', value: log })
})
@ -256,7 +249,7 @@ export class CircomPluginClient extends PluginClient {
const witness = this.compiler ? await this.compiler.generate_witness(dataRead, input) : await generate_witness(dataRead, input)
// @ts-ignore
await this.call('fileManager', 'writeFile', wasmPath.replace('.wasm', '.wtn'), witness, true)
_paq.push(['trackEvent', 'circuit-compiler', 'computeWitness', 'Witness computing successful'])
this._paq.push(['trackEvent', 'circuit-compiler', 'computeWitness', 'Witness computing successful'])
this.internalEvents.emit('circuit_computing_witness_done')
this.emit('statusChanged', { key: 'succeed', title: 'witness computed successfully', type: 'success' })
}

@ -2,7 +2,19 @@ import { compiler_list } from 'circom_wasm'
import { Dispatch } from 'react'
import type { CircomPluginClient } from '../services/circomPluginClient'
export type CompilerStatus = "compiling" | "generating" | "computing" | "idle" | "errored" | "warning"
export type CompilerStatus = "compiling" | "computing" | "idle" | "errored" | "warning" | "exporting" | "proving"
export type ProvingScheme = 'groth16' | 'plonk'
export type SetupExportStatus = 'done' | 'update'
export type PtauFile = {
name: string,
power: number,
maxConstraint: string,
ipfsHash: string,
blake2bHash: string
}
export interface ICircuitAppContext {
appState: AppState
dispatch: Dispatch<Actions>,
@ -17,8 +29,19 @@ export interface ActionPayloadTypes {
SET_AUTO_COMPILE: boolean,
SET_HIDE_WARNINGS: boolean,
SET_SIGNAL_INPUTS: string[],
SET_COMPILER_FEEDBACK: string | CompilerReport[]
SET_FILE_PATH_TO_ID: Record<number, string>
SET_COMPILER_FEEDBACK: string | CompilerReport[],
SET_COMPUTE_FEEDBACK: string | CompilerReport[],
SET_PROOF_FEEDBACK: string | CompilerReport[],
SET_SETUP_EXPORT_FEEDBACK: string | CompilerReport[],
SET_FILE_PATH_TO_ID: Record<number, string>,
SET_PROVING_SCHEME: ProvingScheme,
SET_PTAU_VALUE: string,
SET_EXPORT_VERIFICATION_CONTRACT: boolean,
SET_EXPORT_VERIFICATION_KEY: boolean,
SET_EXPORT_VERIFIER_CALLDATA: boolean,
SET_SETUP_EXPORT_STATUS: SetupExportStatus,
SET_VERIFICATION_KEY: Record<string, any>,
SET_ZKEY: any
}
export interface Action<T extends keyof ActionPayloadTypes> {
type: T
@ -37,7 +60,19 @@ export interface AppState {
autoCompile: boolean,
hideWarnings: boolean,
signalInputs: string[],
feedback: string | CompilerReport[]
compilerFeedback: string | CompilerReport[],
computeFeedback: string | CompilerReport[],
proofFeedback: string | CompilerReport[],
setupExportFeedback: string | CompilerReport[],
setupExportStatus: SetupExportStatus,
provingScheme: ProvingScheme,
ptauList: Array<PtauFile>,
ptauValue: string,
exportVerificationContract: boolean,
exportVerificationKey: boolean,
exportVerifierCalldata: boolean,
verificationKey: Record<string, any>,
zKey: Uint8Array
}
export type CompilationConfig = {

@ -37,8 +37,6 @@ body {
text-transform: uppercase;
}
.circuit_errors_box {
padding-left: 5px;
padding-right: 5px;
word-break: break-word;
}
.circuit_feedback.success,

@ -4,7 +4,7 @@
"displayName": "Circom ZKP compiler",
"events": [],
"version": "2.0.0",
"methods": ["init", "parse", "compile", "generateR1cs"],
"methods": ["init", "parse", "compile"],
"canActivate": [],
"url": "",
"description": "Enables circuit compilation and computing a witness for ZK proofs",

@ -2,6 +2,7 @@
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"jsx": "react-jsx",
"lib": ["ES2015", "DOM", "ES2016", "ES2018"],
"allowJs": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true

@ -18,12 +18,14 @@ export const scanAPIurls = {
59144: 'https://api.lineascan.build/api',
8453: 'https://api.basescan.org/api',
534352: 'https://api.scrollscan.com/api',
1116: 'https://openapi.coredao.org/api',
// all testnet
17000: 'https://api-holesky.etherscan.io/api',
11155111: 'https://api-sepolia.etherscan.io/api',
97: 'https://api-testnet.bscscan.com/api',
80001: 'https://api-testnet.polygonscan.com/api',
80002: 'https://api-amoy.polygonscan.com/api',
4002: 'https://api-testnet.ftmscan.com/api',
421611: 'https://api-testnet.arbiscan.io/api',
42170: 'https://api-nova.arbiscan.io/api',
@ -37,6 +39,8 @@ export const scanAPIurls = {
84531: 'https://api-goerli.basescan.org/api',
84532: "https://api-sepolia.basescan.org/api",
1442: 'https://api-testnet-zkevm.polygonscan.com/api',
2442: 'https://api-cardona-zkevm.polygonscan.com/api',
59140: 'https://api-testnet.lineascan.build/api',
534351: 'https://api-sepolia.scrollscan.com/api',
1115: 'https://api.test.btcs.network/api',
}

@ -13,7 +13,7 @@ export const CaptureKeyView = () => {
const context = React.useContext(AppContext)
useEffect(() => {
if (!context.apiKey) setMsg('Please provide a 34-character API key to continue')
if (!context.apiKey) setMsg('Please provide a 34 or 32 character API key to continue')
}, [context.apiKey])
return (
@ -24,14 +24,14 @@ export const CaptureKeyView = () => {
const errors = {} as any
if (!values.apiKey) {
errors.apiKey = 'Required'
} else if (values.apiKey.length !== 34) {
errors.apiKey = 'API key should be 34 characters long'
} else if (values.apiKey.length !== 34 && values.apiKey.length !== 32) {
errors.apiKey = 'API key should be 34 or 32 characters long'
}
return errors
}}
onSubmit={(values) => {
const apiKey = values.apiKey
if (apiKey.length === 34) {
if (apiKey.length === 34 || apiKey.length === 32) {
context.setAPIKey(values.apiKey)
navigate(location && location.state ? location.state : '/')
}

@ -211,7 +211,7 @@ export const VerifyView = ({apiKey, client, contracts, onVerifiedContract, netwo
type="button"
className="mr-2 mb-2 py-1 px-2 btn btn-secondary btn-block"
onClick={async () => {
etherscanScripts(client)
etherscanScripts({}, client)
}}
>
Generate Verification Scripts

@ -31,9 +31,11 @@ function BackButton({entity}: any) {
</li>
{isDetailPage && (
<li className="nav-item">
<Link className="btn" to={`/list?id=${entity.id}`} title="Tutorial menu" onClick={() => (window as any)._paq.push(['trackEvent', 'learneth', 'back_to_menu_step', entity && entity.name])}>
<i className="fas fa-bars" />
</Link>
<OverlayTrigger placement="right" overlay={<Tooltip id="tooltip-rightTutorialMenu">Tutorial menu</Tooltip>}>
<Link className="btn" to={`/list?id=${entity.id}`} onClick={() => (window as any)._paq.push(['trackEvent', 'learneth', 'back_to_menu_step', entity && entity.name])}>
<i className="fas fa-bars" />
</Link>
</OverlayTrigger>
</li>
)}
</ul>

@ -1,37 +1,57 @@
import React, {useEffect} from 'react'
import {useLocation, useNavigate} from 'react-router-dom'
import React, { useEffect } from 'react'
import { useLocation, useNavigate } from 'react-router-dom'
import Markdown from 'react-markdown'
import rehypeRaw from 'rehype-raw'
import BackButton from '../../components/BackButton'
import {useAppSelector, useAppDispatch} from '../../redux/hooks'
import { useAppSelector, useAppDispatch } from '../../redux/hooks'
import './index.scss'
import remixClient from '../../remix-client'
function StepDetailPage() {
const navigate = useNavigate()
const location = useLocation()
const dispatch = useAppDispatch()
const [clonedStep, setClonedStep] = React.useState(null)
const queryParams = new URLSearchParams(location.search)
const id = queryParams.get('id') as string
const stepId = Number(queryParams.get('stepId'))
const {
workshop: {detail, selectedId},
remixide: {errorLoadingFile, errors, success},
workshop: { detail, selectedId },
remixide: { errorLoadingFile, errors, success },
} = useAppSelector((state: any) => state)
const entity = detail[selectedId].entities[id]
const steps = entity.steps
const step = steps[stepId]
console.log(step)
useEffect(() => {
dispatch({
type: 'remixide/displayFile',
payload: step,
})
dispatch({
type: 'remixide/save',
payload: {errors: [], success: false},
setClonedStep(null)
const clonedStep = JSON.parse(JSON.stringify(step))
const loadFiles = async () => {
async function loadFile(step, fileType) {
if (step[fileType] && step[fileType].file && !step[fileType].content) {
clonedStep[fileType].content = (await remixClient.call('contentImport', 'resolve', step[fileType].file)).content;
}
}
const fileTypes = ['markdown', 'solidity', 'test', 'answer', 'js', 'vy'];
for (const fileType of fileTypes) {
await loadFile(step, fileType);
}
}
loadFiles().then(() => {
setClonedStep(clonedStep)
dispatch({
type: 'remixide/displayFile',
payload: clonedStep,
})
dispatch({
type: 'remixide/save',
payload: { errors: [], success: false },
})
window.scrollTo(0, 0)
})
window.scrollTo(0, 0)
}, [step])
useEffect(() => {
@ -40,8 +60,20 @@ function StepDetailPage() {
}
}, [errors, success])
if (!clonedStep) {
return (<div className='pb-4'>
<div className="fixed-top">
<div className="bg-light">
<BackButton entity={entity} />
</div>
</div>
loading...
</div>
)
}
return (
<>
<div className='pb-4'>
<div className="fixed-top">
<div className="bg-light">
<BackButton entity={entity} />
@ -51,13 +83,13 @@ function StepDetailPage() {
{errorLoadingFile ? (
<>
<div className="errorloadingspacer"></div>
<h1 className="pl-3 pr-3 pt-3 pb-1">{step.name}</h1>
<h1 className="pl-3 pr-3 pt-3 pb-1">{clonedStep.name}</h1>
<button
className="w-100nav-item rounded-0 nav-link btn btn-success test"
onClick={() => {
dispatch({
type: 'remixide/displayFile',
payload: step,
payload: clonedStep,
})
}}
>
@ -68,13 +100,13 @@ function StepDetailPage() {
) : (
<>
<div className="menuspacer"></div>
<h1 className="pr-3 pl-3 pt-3 pb-1">{step.name}</h1>
<h1 className="pr-3 pl-3 pt-3 pb-1">{clonedStep.name}</h1>
</>
)}
<div className="container-fluid">
<Markdown rehypePlugins={[rehypeRaw]}>{step.markdown?.content}</Markdown>
<Markdown rehypePlugins={[rehypeRaw]}>{clonedStep.markdown?.content}</Markdown>
</div>
{step.test?.content ? (
{clonedStep.test?.content ? (
<>
<nav className="nav nav-pills nav-fill">
{errorLoadingFile ? (
@ -83,7 +115,7 @@ function StepDetailPage() {
onClick={() => {
dispatch({
type: 'remixide/displayFile',
payload: step,
payload: clonedStep,
})
}}
>
@ -98,19 +130,19 @@ function StepDetailPage() {
onClick={() => {
dispatch({
type: 'remixide/testStep',
payload: step,
payload: clonedStep,
})
}}
>
Check Answer
</button>
{step.answer?.content && (
{clonedStep.answer?.content && (
<button
className="nav-item rounded-0 nav-link btn btn-warning test"
onClick={() => {
dispatch({
type: 'remixide/showAnswer',
payload: step,
payload: clonedStep,
})
}}
>
@ -130,13 +162,13 @@ function StepDetailPage() {
>
Next
</button>
{step.answer?.content && (
{clonedStep.answer?.content && (
<button
className="nav-item rounded-0 nav-link btn btn-warning test"
onClick={() => {
dispatch({
type: 'remixide/showAnswer',
payload: step,
payload: clonedStep,
})
}}
>
@ -185,13 +217,13 @@ function StepDetailPage() {
) : (
<>
<nav className="nav nav-pills nav-fill">
{!errorLoadingFile && step.answer?.content && (
{!errorLoadingFile && clonedStep.answer?.content && (
<button
className="nav-item rounded-0 nav-link btn btn-warning test"
onClick={() => {
dispatch({
type: 'remixide/showAnswer',
payload: step,
payload: clonedStep,
})
}}
>
@ -223,7 +255,7 @@ function StepDetailPage() {
)}
</>
)}
</>
</div>
)
}

@ -84,7 +84,6 @@ const Model: ModelType = {
const { detail, selectedId } = yield select((state) => state.workshop)
const workshop = detail[selectedId]
console.log('loading ', step, workshop)
path = `.learneth/${workshop.name}/${step.name}/${path}`
try {
@ -138,14 +137,11 @@ const Model: ModelType = {
yield remixClient.call('fileManager', 'switchFile', `${path}`)
}
console.log('testing ', step.test.content)
path = getFilePath(step.test.file)
path = `.learneth/${workshop.name}/${step.name}/${path}`
yield remixClient.call('fileManager', 'setFile', path, step.test.content)
const result = yield remixClient.call('solidityUnitTesting', 'testFromPath', path)
console.log('result ', result);
if (!result) {
yield put({
@ -196,7 +192,6 @@ const Model: ModelType = {
toast.info('loading answer into IDE')
try {
console.log('loading ', step)
const content = step.answer.content
let path = getFilePath(step.answer.file)

@ -23,7 +23,7 @@ const Model: ModelType = {
},
effects: {
*init(_, { put }) {
const cache = localStorage.getItem('workshop.state')
const cache = null // don't use cache because remote might change
if (cache) {
const workshopState = JSON.parse(cache)
@ -54,7 +54,6 @@ const Model: ModelType = {
const { list, detail } = yield select((state) => state.workshop)
const url = `${apiUrl}/clone/${encodeURIComponent(payload.name)}/${payload.branch}?${Math.random()}`
console.log('loading ', url)
const { data } = yield axios.get(url)
const repoId = `${payload.name}-${payload.branch}`
@ -90,7 +89,7 @@ const Model: ModelType = {
const key = stepKeysWithFile[k]
if (step[key]) {
try {
step[key].content = (yield remixClient.call('contentImport', 'resolve', step[key].file)).content
step[key].content = null // we load this later
} catch (error) {
console.error(error)
}

@ -10,7 +10,6 @@ class RemixClient extends PluginClient {
}
startTutorial(name: any, branch: any, id: any): void {
console.log('start tutorial', name, branch, id)
void router.navigate('/home')
store.dispatch({
type: 'workshop/loadRepo',
@ -23,7 +22,6 @@ class RemixClient extends PluginClient {
}
addRepository(name: any, branch: any) {
console.log('add repo', name, branch)
void router.navigate('/home')
store.dispatch({
type: 'workshop/loadRepo',

@ -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,4 +1,4 @@
import {NightwatchBrowser} from 'nightwatch'
import { NightwatchBrowser } from 'nightwatch'
import EventEmitter from 'events'
class RefreshPage extends EventEmitter {

@ -8,15 +8,14 @@ class SelectFiles extends EventEmitter {
browser.perform(function () {
const actions = this.actions({ async: true })
actions.keyDown(this.Keys.SHIFT)
for(let i = 0; i < selectedElements.length; i++) {
for (let i = 0; i < selectedElements.length; i++) {
actions.click(selectedElements[i].value)
}
return actions.contextClick(selectedElements[0].value)
return actions//.contextClick(selectedElements[0].value)
})
this.emit('complete')
return this
}
}
module.exports = SelectFiles

@ -16,6 +16,7 @@ class MetaMask extends EventEmitter {
function setupMetaMask(browser: NightwatchBrowser, passphrase: string, password: string, done: VoidFunction) {
const words = passphrase.split(' ')
console.log('setup metamask')
browser
.switchBrowserTab(1)
.waitForElementVisible('input[data-testid="onboarding-terms-checkbox"]')
@ -49,6 +50,7 @@ function setupMetaMask(browser: NightwatchBrowser, passphrase: string, password:
.click('button[data-testid="pin-extension-next"]')
.waitForElementVisible('button[data-testid="pin-extension-done"]')
.click('button[data-testid="pin-extension-done"]')
.pause(5000)
.isVisible({
selector: 'button[data-testid="popover-close"]',
locateStrategy: 'css selector',
@ -58,14 +60,22 @@ function setupMetaMask(browser: NightwatchBrowser, passphrase: string, password:
console.log('okVisible', okVisible)
if (!okVisible.value) {
console.log('popover not found')
}else{
} else {
console.log('popover found... closing')
browser.click('button[data-testid="popover-close"]')
}
})
.waitForElementNotPresent({
selector: 'button[data-testid="popover-close"]',
locateStrategy: 'css selector',
timeout: 3000
})
.saveScreenshot('./reports/screenshots/metamask.png')
.click('[data-testid="network-display"]')
.click('.mm-modal-content label.toggle-button--off') // show test networks
.click('div[data-testid="Sepolia"]') // switch to sepolia
.perform(() => {
console.log('MetaMask setup complete')
done()
})
}

@ -2,37 +2,37 @@ import { NightwatchBrowser } from 'nightwatch'
import EventEmitter from 'events'
class VerifyLoad extends EventEmitter {
command(this: NightwatchBrowser) {
browser.waitForElementPresent({
selector: "//span[@data-id='typesloaded']",
locateStrategy: 'xpath',
timeout: 60000
})
.waitForElementPresent({
selector: "//span[@data-id='editorloaded']",
locateStrategy: 'xpath',
timeout: 60000
})
.waitForElementPresent({
selector: "//span[@data-id='workspaceloaded']",
locateStrategy: 'xpath',
timeout: 60000
})
.waitForElementPresent({
selector: "//span[@data-id='apploaded']",
locateStrategy: 'xpath',
timeout: 60000
})
.waitForElementPresent({
selector: "//span[@data-id='compilerloaded']",
locateStrategy: 'xpath',
timeout: 120000
})
.perform((done) => {
done()
this.emit('complete')
})
}
command(this: NightwatchBrowser) {
browser.waitForElementPresent({
selector: "//span[@data-id='typesloaded']",
locateStrategy: 'xpath',
timeout: 60000
})
.waitForElementPresent({
selector: "//span[@data-id='editorloaded']",
locateStrategy: 'xpath',
timeout: 60000
})
.waitForElementPresent({
selector: "//span[@data-id='workspaceloaded']",
locateStrategy: 'xpath',
timeout: 60000
})
.waitForElementPresent({
selector: "//span[@data-id='apploaded']",
locateStrategy: 'xpath',
timeout: 60000
})
.waitForElementPresent({
selector: "//span[@data-id='compilerloaded']",
locateStrategy: 'xpath',
timeout: 120000
})
.perform((done) => {
done()
this.emit('complete')
})
}
}
module.exports = VerifyLoad
module.exports = VerifyLoad

@ -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

@ -1,3 +1,4 @@
/* eslint-disable prefer-rest-params */
import { NightwatchBrowser } from 'nightwatch'
require('dotenv').config()
@ -7,6 +8,7 @@ type LoadPlugin = {
url: string
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export default function (browser: NightwatchBrowser, callback: VoidFunction, url?: string, preloadPlugins = true, loadPlugin?: LoadPlugin, hideToolTips: boolean = true): void {
browser
.url(url || 'http://127.0.0.1:8080')
@ -26,50 +28,50 @@ export default function (browser: NightwatchBrowser, callback: VoidFunction, url
.verifyLoad()
.enableClipBoard()
.perform((done) => {
browser.execute(function () { // hide tooltips
function addStyle(styleString) {
const style = document.createElement('style');
style.textContent = styleString;
document.head.append(style);
}
browser.execute(function () { // hide tooltips
function addStyle(styleString) {
const style = document.createElement('style');
style.textContent = styleString;
document.head.append(style);
}
addStyle(`
addStyle(`
.popover {
display:none !important;
}
`);
}, [], done())
})
.perform(() => {
browser.execute(function () {
(window as any).logs = [];
(console as any).browserLog = console.log;
(console as any).browserError = console.error
console.log = function () {
(window as any).logs.push(JSON.stringify(arguments));
(console as any).browserLog(...arguments)
}
console.error = function () {
(window as any).logs.push(JSON.stringify(arguments));
(console as any).browserError(...arguments)
}
})
})
.perform(() => {
if (preloadPlugins) {
initModules(browser, () => {
browser
.pause(4000)
.clickLaunchIcon('solidity')
.waitForElementVisible('[for="autoCompile"]')
.click('[for="autoCompile"]')
.verify.elementPresent('[data-id="compilerContainerAutoCompile"]:checked')
.perform(() => { callback() })
})
} else {
callback()
}, [], done())
})
.perform(() => {
browser.execute(function () {
(window as any).logs = [];
(console as any).browserLog = console.log;
(console as any).browserError = console.error
console.log = function () {
(window as any).logs.push(JSON.stringify(arguments));
(console as any).browserLog(...arguments)
}
console.error = function () {
(window as any).logs.push(JSON.stringify(arguments));
(console as any).browserError(...arguments)
}
})
})
.perform(() => {
if (preloadPlugins) {
initModules(browser, () => {
browser
.pause(4000)
.clickLaunchIcon('solidity')
.waitForElementVisible('[for="autoCompile"]')
.click('[for="autoCompile"]')
.verify.elementPresent('[data-id="compilerContainerAutoCompile"]:checked')
.perform(() => { callback() })
})
} else {
callback()
}
})
}
function initModules(browser: NightwatchBrowser, callback: VoidFunction) {

@ -97,12 +97,12 @@ module.exports = {
.clickLaunchIcon('filePanel')
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementPresent('*[data-id="create-remixDefault"]')
.scrollAndClick('*[data-id="create-remixDefault"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_remix_default' })
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_remix_default')
.modalFooterOKClick('TemplatesSelection')
.pause(1000)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]')
.addFile('contracts/lib/storage/src/Storage.sol', { content: storageContract})

@ -13,13 +13,9 @@ module.exports = {
.clickLaunchIcon('filePanel')
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
.click('select[id="wstemplate"]')
.click('select[id="wstemplate"] option[value=semaphore]')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.pause(100)
.waitForElementPresent('*[data-id="create-semaphore"]')
.scrollAndClick('*[data-id="create-semaphore"]')
.modalFooterOKClick('TemplatesSelection')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcircuits"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcircuits/semaphore.circom"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts"]')
@ -76,17 +72,36 @@ module.exports = {
.waitForElementPresent('[data-id="treeViewLitreeViewItemcircuits/.bin/simple.wasm"]')
.waitForElementVisible('[data-id="treeViewLitreeViewItemcircuits/.bin/simple.wasm"]')
},
'Should generate R1CS for a simple circuit #group2': function (browser: NightwatchBrowser) {
'Should run Groth16 setup and export for a simple circuit using the GUI #group2': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('circuit-compiler')
.frame(0)
.waitForElementVisible('[data-id="setup_exports_toggler"]')
.waitForElementPresent('[data-id="groth16ProvingScheme"]')
.click('[data-id="groth16ProvingScheme"]')
.waitForElementVisible('[data-id="circuitPtauSelect"]')
.click('[data-id="circuitPtauSelect"]')
.waitForElementVisible('[data-id="dropdown-item-final_8.ptau"]')
.click('[data-id="dropdown-item-final_8.ptau"]')
.click('[data-id="runSetupBtn"]')
.waitForElementVisible('[data-id="setup_exports_toggler"] .fa-check-circle')
.frameParent()
.clickLaunchIcon('filePanel')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcircuits/groth16/zk/keys/verification_key.json"]')
},
'Should run Plonk setup and export for a simple circuit using the GUI #group2': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('circuit-compiler')
.frame(0)
.waitForElementPresent('button[data-id="generate_r1cs_btn"]')
.waitForElementVisible('button[data-id="generate_r1cs_btn"]')
.click('button[data-id="generate_r1cs_btn"]')
.waitForElementVisible('[data-id="setup_exports_toggler"]')
.click('[data-id="setup_exports_toggler"]')
.waitForElementPresent('[data-id="plonkProvingScheme"]')
.click('[data-id="plonkProvingScheme"]')
.click('[data-id="runSetupBtn"]')
.waitForElementVisible('[data-id="setup_exports_toggler"] .fa-check-circle')
.frameParent()
.clickLaunchIcon('filePanel')
.waitForElementPresent('[data-id="treeViewLitreeViewItemcircuits/.bin/simple.r1cs"]')
.waitForElementVisible('[data-id="treeViewLitreeViewItemcircuits/.bin/simple.r1cs"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcircuits/plonk/zk/keys/verification_key.json"]')
},
'Should compile a simple circuit using CTRL + S from the editor #group3': function (browser: NightwatchBrowser) {
browser
@ -155,12 +170,9 @@ module.exports = {
.clickLaunchIcon('filePanel')
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
.click('select[id="wstemplate"]')
.click('select[id="wstemplate"] option[value=hashchecker]')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.waitForElementPresent('*[data-id="create-hashchecker"]')
.scrollAndClick('*[data-id="create-hashchecker"]')
.modalFooterOKClick('TemplatesSelection')
.pause(100)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcircuits"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcircuits/calculate_hash.circom"]')
@ -183,15 +195,11 @@ module.exports = {
.waitForElementPresent('[data-id="verticalIconsKindcircuit-compiler"]')
.waitForElementVisible('[data-id="verticalIconsKindcircuit-compiler"]')
.click('[data-id="play-editor"]')
.pause(2000)
.journalLastChildIncludes('Generating R1CS for circuits/calculate_hash.circom')
.pause(5000)
.journalLastChildIncludes('Everything went okay')
.pause(7000)
.journalLastChildIncludes('newZkey')
.pause(25000)
.journalLastChildIncludes('setup done.')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemzk/keys/groth16/verification_key.json"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemzk/keys/groth16/zkey_final.txt"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/groth16/zk/keys/verification_key.json"]')
},
'Should run groth16 zkproof script for hash checker #group5': function (browser: NightwatchBrowser) {
browser
@ -210,8 +218,8 @@ module.exports = {
.journalLastChildIncludes('WITNESS CHECKING FINISHED SUCCESSFULLY')
.pause(2000)
.journalLastChildIncludes('zk proof validity')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemzk/build/groth16/zk_verifier.sol"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemzk/build/groth16/input.json"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/groth16/zk/build/zk_verifier.sol"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/groth16/zk/build/input.json"]')
},
'Should run plonk trusted setup script for hash checker #group6': function (browser: NightwatchBrowser) {
browser
@ -221,15 +229,11 @@ module.exports = {
.waitForElementPresent('[data-id="verticalIconsKindcircuit-compiler"]')
.waitForElementVisible('[data-id="verticalIconsKindcircuit-compiler"]')
.click('[data-id="play-editor"]')
.pause(2000)
.journalLastChildIncludes('Generating R1CS for circuits/calculate_hash.circom')
.pause(5000)
.journalLastChildIncludes('Everything went okay')
.pause(7000)
.journalLastChildIncludes('plonk setup')
.pause(10000)
.journalLastChildIncludes('setup done')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemzk/keys/plonk/verification_key.json"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemzk/keys/plonk/zkey_final.txt"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/plonk/zk/keys/verification_key.json"]')
},
'Should run plonk zkproof script for hash checker #group6': function (browser: NightwatchBrowser) {
browser
@ -246,8 +250,8 @@ module.exports = {
.pause(5000)
.journalLastChildIncludes('zk proof validity')
.journalLastChildIncludes('proof done.')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemzk/build/plonk/zk_verifier.sol"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemzk/build/plonk/input.json"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/plonk/zk/build/zk_verifier.sol"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/plonk/zk/build/input.json"]')
}
}

@ -101,8 +101,8 @@ module.exports = {
.click('*[data-id="remotes-panel"]')
.waitForElementVisible('*[data-id="remotes-panel-content"]')
.click({
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-detail-origin"]',
.waitForElementVisible({
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-detail-origin-default"]',
locateStrategy: 'xpath'
})
.waitForElementVisible({
@ -115,7 +115,8 @@ module.exports = {
})
.waitForElementVisible({
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="branches-branch-links"]',
locateStrategy: 'xpath'
locateStrategy: 'xpath',
timeout: 10000
})
},
@ -136,24 +137,24 @@ module.exports = {
},
'switch to branch links #group1': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="branches-panel"]')
.waitForElementVisible({
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="branches-branch-links"]',
selector: '//*[@data-id="branches-panel-content-remote-branches"]//*[@data-id="branches-branch-links"]',
locateStrategy: 'xpath'
})
.click({
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="branches-toggle-branch-links"]',
selector: '//*[@data-id="branches-panel-content-remote-branches"]//*[@data-id="branches-toggle-branch-links"]',
locateStrategy: 'xpath'
})
.waitForElementVisible({
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="branches-toggle-current-branch-links"]',
selector: '//*[@data-id="branches-panel-content-remote-branches"]//*[@data-id="branches-toggle-current-branch-links"]',
locateStrategy: 'xpath'
})
},
'check the local branches #group1': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="branches-panel"]')
.waitForElementVisible({
selector: '//*[@data-id="branches-panel-content"]//*[@data-id="branches-toggle-current-branch-links"]',
selector: '//*[@data-id="branches-panel-content-local-branches"]//*[@data-id="branches-toggle-current-branch-links"]',
locateStrategy: 'xpath'
})
},
@ -232,7 +233,7 @@ module.exports = {
locateStrategy: 'xpath'
})
.waitForElementVisible({
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-detail-newremote"]',
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-detail-newremote-default"]',
locateStrategy: 'xpath'
})
},
@ -263,7 +264,7 @@ module.exports = {
}
})
},
'remove the remove #group2': function (browser: NightwatchBrowser) {
'remove the remote #group2': function (browser: NightwatchBrowser) {
browser
.pause(1000)
.click('*[data-id="remotes-panel"]')
@ -278,7 +279,7 @@ module.exports = {
})
.pause(1000)
.waitForElementNotPresent({
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-detail-newremote"]',
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-detail-newremote-default"]',
locateStrategy: 'xpath'
})
},

@ -24,14 +24,14 @@ module.exports = {
})
},
'run server #group1 #group2 #group3': function (browser: NightwatchBrowser) {
'run server #group1 #group2 #group3 #group4': function (browser: NightwatchBrowser) {
browser.perform(async (done) => {
gitserver = await spawnGitServer('/tmp/')
console.log('working directory', process.cwd())
done()
})
},
'Update settings for git #group1 #group2 #group3': function (browser: NightwatchBrowser) {
'Update settings for git #group1 #group2 #group3 #group4': function (browser: NightwatchBrowser) {
browser.
clickLaunchIcon('dgit')
.waitForElementVisible('*[data-id="initgit-btn"]')
@ -42,7 +42,7 @@ module.exports = {
.modalFooterOKClick('github-credentials-error')
.pause(2000)
},
'clone a repo #group1 #group2 #group3': function (browser: NightwatchBrowser) {
'clone a repo #group1 #group2 #group3 #group4': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="clone-panel"]')
.click('*[data-id="clone-panel"]')
@ -56,10 +56,11 @@ module.exports = {
// GROUP 1
'check file added #group1 #group3': function (browser: NightwatchBrowser) {
'check file added #group1 #group3 #group4': function (browser: NightwatchBrowser) {
browser.
addFile('test.txt', { content: 'hello world' }, 'README.md')
.clickLaunchIcon('dgit')
.pause(3000)
.click('*[data-id="sourcecontrol-panel"]')
.waitForElementVisible({
selector: "//*[@data-status='new-untracked' and @data-file='/test.txt']",
@ -75,7 +76,7 @@ module.exports = {
.setValue('*[data-id="commitMessage"]', 'testcommit')
.click('*[data-id="commitButton"]')
},
'look at the commit #group1': function (browser: NightwatchBrowser) {
'look at the commit #group1 #group4': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="commits-panel"]')
.waitForElementPresent({
@ -187,6 +188,7 @@ module.exports = {
'stage renamed file #group3': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('dgit')
.pause(3000)
.waitForElementVisible({
selector: "//*[@data-status='deleted-unstaged' and @data-file='/test.txt']",
locateStrategy: 'xpath'
@ -228,6 +230,7 @@ module.exports = {
'create a branch #group2': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('dgit')
.pause(3000)
.click('*[data-id="branches-panel"]')
.waitForElementVisible('*[data-id="newbranchname"]')
.setValue('*[data-id="newbranchname"]', 'testbranch')
@ -244,6 +247,7 @@ module.exports = {
'publish the branch #group2': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('dgit')
.pause(3000)
.waitForElementVisible('*[data-id="sourcecontrol-panel"]')
.click('*[data-id="sourcecontrol-panel"]')
.pause(1000)
@ -321,9 +325,97 @@ module.exports = {
},
'check if test file is gone #group2': function (browser: NightwatchBrowser) {
browser
.pause()
.clickLaunchIcon('filePanel')
.waitForElementNotPresent('*[data-id="treeViewLitreeViewItemtest.txt"]')
}
},
'add second remote #group4': function (browser: NightwatchBrowser) {
browser
.pause(1000)
.click('*[data-id="remotes-panel"]')
.waitForElementVisible('*[data-id="add-manual-remoteurl"]')
.setValue('*[data-id="add-manual-remoteurl"]', 'http://localhost:6868/bare2.git')
.waitForElementVisible('*[data-id="add-manual-remotename"]')
.setValue('*[data-id="add-manual-remotename"]', 'origin2')
.waitForElementVisible('*[data-id="add-manual-remotebtn"]')
.click('*[data-id="add-manual-remotebtn"]')
},
'check the buttons #group4': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="default-remote-check-origin"]')
.waitForElementVisible('*[data-id="set-as-default-origin2"]')
},
'check the commands #group4': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="commands-panel"]')
.waitForElementVisible({
selector: "//div[@id='commands-remote-origin-select']//div[contains(@class, 'singleValue') and contains(text(), 'origin')]",
locateStrategy: 'xpath'
})
},
'switch to origin2 #group4': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="remotes-panel"]')
.waitForElementVisible('*[data-id="set-as-default-origin2"]')
.click('*[data-id="set-as-default-origin2"]')
},
'check the commands for origin2 #group4': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="commands-panel"]')
.waitForElementVisible({
selector: "//div[@id='commands-remote-origin-select']//div[contains(@class, 'singleValue') and contains(text(), 'origin2')]",
locateStrategy: 'xpath'
})
},
'sync the commit #group4': function (browser: NightwatchBrowser) {
browser
.pause(1000)
.waitForElementVisible('*[data-id="sourcecontrol-panel"]')
.click('*[data-id="sourcecontrol-panel"]')
.waitForElementVisible('*[data-id="syncButton"]')
.click('*[data-id="syncButton"]')
.waitForElementVisible('*[data-id="commitButton"]')
.click('*[data-id="commits-panel"]')
.waitForElementPresent({
selector: '//*[@data-id="commit-summary-testcommit-"]',
locateStrategy: 'xpath'
})
},
'check the log #group4': async function (browser: NightwatchBrowser) {
const logs = await getGitLog('/tmp/git/bare2.git')
console.log(logs)
browser.assert.ok(logs.includes('testcommit'))
const logs2 = await getGitLog('/tmp/git/bare.git')
console.log(logs2)
browser.assert.fail(logs2.includes('testcommit'))
},
'switch to origin #group4': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="remotes-panel"]')
.waitForElementVisible('*[data-id="set-as-default-origin"]')
.click('*[data-id="set-as-default-origin"]')
},
'check the commands for origin #group4': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="commands-panel"]')
.waitForElementVisible({
selector: "//div[@id='commands-remote-origin-select']//div[contains(@class, 'singleValue') and contains(text(), 'origin')]",
locateStrategy: 'xpath'
})
},
'check the commit ahead #group4': function (browser: NightwatchBrowser) {
browser
.pause(1000)
.waitForElementVisible('*[data-id="sourcecontrol-panel"]')
.click('*[data-id="sourcecontrol-panel"]')
.waitForElementVisible('*[data-id="syncButton"]')
// do not sync
.click('*[data-id="commits-panel"]')
.waitForElementPresent({
selector: '//*[@data-id="commit-summary-testcommit-ahead"]',
locateStrategy: 'xpath'
})
},
}
async function getBranches(path: string): Promise<string> {
@ -360,10 +452,10 @@ async function getGitLog(path: string): Promise<string> {
})
}
async function cloneOnServer(repo: string, path: string) {
async function cloneOnServer(repo: string, path: string, name: string = 'bare') {
console.log('cloning', repo, path)
return new Promise((resolve, reject) => {
const git = spawn('rm -rf bare && git', ['clone', repo], { cwd: path, shell: true, detached: true });
const git = spawn(`rm -rf ${name} && git`, ['clone', repo], { cwd: path, shell: true, detached: true });
git.stdout.on('data', function (data) {
console.log('stdout data cloning', data.toString());

@ -17,14 +17,12 @@ module.exports = {
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
// create contract
.waitForElementPresent('*[data-id="create-hashchecker"]')
.scrollAndClick('*[data-id="create-ozerc721"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_erc721' })
.click('select[id="wstemplate"]')
.click('select[id="wstemplate"] option[value=ozerc721]')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_erc721')
.modalFooterOKClick('TemplatesSelection')
.pause(100)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/MyToken.sol"]')

@ -18,6 +18,7 @@ module.exports = {
.click('*[data-id="fileExplorerNewFilecreateNewFile"]')
.pause(1000)
.waitForElementVisible('*[data-id$="fileExplorerTreeItemInput"]')
.pause(1000)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.CONTROL + 'v')
.pause(1000)
.sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER)
@ -93,7 +94,7 @@ module.exports = {
.rightClick('*[data-id="treeViewUltreeViewMenu"]')
.saveScreenshot('./reports/screenshot/file_explorer_context_menu.png')
.click('*[data-id="contextMenuItempaste"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemCopy_README.txt"]', 7000)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemREADME1.txt"]', 7000)
},
'Should copy file and paste in contracts with right click and it will contain a new file #group1 ': function (browser: NightwatchBrowser) {
browser
@ -103,7 +104,7 @@ module.exports = {
.click('[data-id="contextMenuItemcopy"]')
.rightClick('*[data-id="treeViewLitreeViewItemcontracts"]')
.click('*[data-id="contextMenuItempaste"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/Copy_README.txt"]', 7000)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/README1.txt"]', 7000)
},
// folder copy paste tests
'Should copy folder and paste in root with right click and it will contain a copied folder #group1 ': function (browser: NightwatchBrowser) {

@ -2,6 +2,7 @@ import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
module.exports = {
"@disabled": true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done)
},
@ -10,11 +11,11 @@ module.exports = {
const selectedElements = []
browser
.openFile('contracts')
.click({ selector: '//*[@data-id="treeViewLitreeViewItemcontracts/1_Storage.sol"]', locateStrategy: 'xpath' })
.findElement({ selector: '//*[@data-id="treeViewLitreeViewItemcontracts/2_Owner.sol"]', locateStrategy: 'xpath' }, (el) => {
.click({ selector: '//*[@data-id="treeViewDivtreeViewItemcontracts/1_Storage.sol"]', locateStrategy: 'xpath' })
.findElement({ selector: '//*[@data-id="treeViewDivtreeViewItemcontracts/2_Owner.sol"]', locateStrategy: 'xpath' }, (el) => {
selectedElements.push(el)
})
browser.findElement({ selector: '//*[@data-id="treeViewLitreeViewItemtests"]', locateStrategy: 'xpath' },
browser.findElement({ selector: '//*[@data-id="treeViewDivtreeViewItemtests"]', locateStrategy: 'xpath' },
(el: any) => {
selectedElements.push(el)
})
@ -22,6 +23,74 @@ module.exports = {
.assert.visible('.bg-secondary[data-id="treeViewLitreeViewItemcontracts/1_Storage.sol"]')
.assert.visible('.bg-secondary[data-id="treeViewLitreeViewItemcontracts/2_Owner.sol"]')
.assert.visible('.bg-secondary[data-id="treeViewLitreeViewItemtests"]')
.end()
},
'Should drag and drop multiple files in file explorer to tests folder #group1': function (browser: NightwatchBrowser) {
const selectedElements = []
if (browser.options.desiredCapabilities?.browserName === 'firefox') {
console.log('Skipping test for firefox')
browser.end()
return;
} else {
browser
.click({ selector: '//*[@data-id="treeViewUltreeViewMenu"]', locateStrategy: 'xpath' })
.click({ selector: '//*[@data-id="treeViewLitreeViewItemcontracts/1_Storage.sol"]', locateStrategy: 'xpath' })
.findElement({ selector: '//*[@data-id="treeViewLitreeViewItemcontracts/2_Owner.sol"]', locateStrategy: 'xpath' }, (el) => {
selectedElements.push(el)
})
browser.selectFiles(selectedElements)
.perform((done) => {
browser.findElement({ selector: '//*[@data-id="treeViewLitreeViewItemtests"]', locateStrategy: 'xpath' },
(el: any) => {
const id = (el as any).value.getId()
browser
.waitForElementVisible('li[data-id="treeViewLitreeViewItemtests"]')
.dragAndDrop('li[data-id="treeViewLitreeViewItemcontracts/1_Storage.sol"]', id)
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.waitForElementVisible('li[data-id="treeViewLitreeViewItemtests/1_Storage.sol"]')
.waitForElementVisible('li[data-id="treeViewLitreeViewItemtests/2_Owner.sol"]')
.waitForElementNotPresent('li[data-id="treeViewLitreeViewItemcontracts/1_Storage.sol"]')
.waitForElementNotPresent('li[data-id="treeViewLitreeViewItemcontracts/2_Owner.sol"]')
.perform(() => done())
})
})
}
},
'should drag and drop multiple files and folders in file explorer to contracts folder #group3': function (browser: NightwatchBrowser) {
const selectedElements = []
if (browser.options.desiredCapabilities?.browserName === 'firefox') {
console.log('Skipping test for firefox')
browser.end()
return;
} else {
browser
.clickLaunchIcon('filePanel')
.click({ selector: '//*[@data-id="treeViewLitreeViewItemtests"]', locateStrategy: 'xpath' })
.findElement({ selector: '//*[@data-id="treeViewDivtreeViewItemscripts"]', locateStrategy: 'xpath' }, (el) => {
selectedElements.push(el)
})
browser.findElement({ selector: '//*[@data-id="treeViewDivtreeViewItemREADME.txt"]', locateStrategy: 'xpath' },
(el: any) => {
selectedElements.push(el)
})
browser.selectFiles(selectedElements)
.perform((done) => {
browser.findElement({ selector: '//*[@data-id="treeViewLitreeViewItemcontracts"]', locateStrategy: 'xpath' },
(el: any) => {
const id = (el as any).value.getId()
browser
.waitForElementVisible('li[data-id="treeViewLitreeViewItemcontracts"]')
.dragAndDrop('li[data-id="treeViewLitreeViewItemtests"]', id)
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.waitForElementVisible('li[data-id="treeViewLitreeViewItemcontracts/tests"]', 5000)
.waitForElementVisible('li[data-id="treeViewLitreeViewItemcontracts/README.txt"]', 5000)
.waitForElementVisible('li[data-id="treeViewLitreeViewItemcontracts/scripts"]', 5000)
.waitForElementNotPresent('li[data-id="treeViewLitreeViewItemtests"]')
.waitForElementNotPresent('li[data-id="treeViewLitreeViewItemREADME.txt"]')
.perform(() => done())
})
})
}
}
}

@ -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
}
}

@ -11,7 +11,7 @@ const checkBrowserIsChrome = function (browser: NightwatchBrowser) {
return browser.browserName.indexOf('chrome') > -1
}
const checkAlerts = function (browser: NightwatchBrowser){
const checkAlerts = function (browser: NightwatchBrowser) {
browser.isVisible({
selector: '//*[contains(.,"not have enough")]',
locateStrategy: 'xpath',
@ -38,7 +38,7 @@ const tests = {
'Should connect to Sepolia Test Network using MetaMask #group1': function (browser: NightwatchBrowser) {
if (!checkBrowserIsChrome(browser)) return
browser.waitForElementPresent('*[data-id="remixIdeSidePanel"]')
.setupMetamask(passphrase, password)
.setupMetamask(passphrase, password)
.useCss().switchBrowserTab(0)
.refreshPage()
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
@ -50,13 +50,14 @@ const tests = {
.pause(5000)
.switchBrowserWindow(extension_url, 'MetaMask', (browser) => {
browser
.hideMetaMaskPopup()
.waitForElementVisible('*[data-testid="page-container-footer-next"]', 60000)
.click('*[data-testid="page-container-footer-next"]') // this connects the metamask account to remix
.pause(2000)
.waitForElementVisible('*[data-testid="page-container-footer-next"]', 60000)
.click('*[data-testid="page-container-footer-next"]')
// .waitForElementVisible('*[data-testid="popover-close"]')
// .click('*[data-testid="popover-close"]')
// .waitForElementVisible('*[data-testid="popover-close"]')
// .click('*[data-testid="popover-close"]')
})
.switchBrowserTab(0) // back to remix
},
@ -83,6 +84,7 @@ const tests = {
browser.switchBrowserWindow(extension_url, 'MetaMask', (browser) => {
checkAlerts(browser)
browser
.hideMetaMaskPopup()
.waitForElementPresent('[data-testid="page-container-footer-next"]')
.click('[data-testid="page-container-footer-next"]') // approve the tx
.switchBrowserTab(0) // back to remix
@ -90,7 +92,7 @@ const tests = {
.waitForElementContainsText('*[data-id="terminalJournal"]', 'from: 0x76a...2708f', 60000)
.perform(() => done())
})
})
})
},
'Should run low level interaction (fallback function) on Sepolia Test Network using MetaMask #group1': function (browser: NightwatchBrowser) {
@ -102,14 +104,15 @@ const tests = {
.perform((done) => {
browser.switchBrowserWindow(extension_url, 'MetaMask', (browser) => {
browser
.hideMetaMaskPopup()
.waitForElementPresent('[data-testid="page-container-footer-next"]')
.click('[data-testid="page-container-footer-next"]') // approve the tx
.switchBrowserTab(0) // back to remix
.waitForElementContainsText('*[data-id="terminalJournal"]', 'view on etherscan', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'from: 0x76a...2708f', 60000)
.perform(() => done())
})
})
})
})
},
'Should connect to Ethereum Main Network using MetaMask #group1': function (browser: NightwatchBrowser) {
@ -162,6 +165,8 @@ const tests = {
.perform((done) => {
browser.switchBrowserWindow(extension_url, 'MetaMask', (browser) => {
browser
.hideMetaMaskPopup()
.saveScreenshot('./reports/screenshots/metamask_4.png')
.waitForElementPresent('[data-testid="page-container-footer-next"]', 60000)
.click('[data-testid="page-container-footer-next"]') // approve the tx
.switchBrowserTab(0) // back to remix
@ -169,7 +174,7 @@ const tests = {
.waitForElementContainsText('*[data-id="terminalJournal"]', 'from: 0x76a...2708f', 60000)
.perform(() => done())
})
})
})
.waitForElementPresent('*[data-id="universalDappUiContractActionWrapper"]', 60000)
.clearConsole()
.clickInstance(0)
@ -177,6 +182,8 @@ const tests = {
.perform((done) => { // call delegate
browser.switchBrowserWindow(extension_url, 'MetaMask', (browser) => {
browser
.hideMetaMaskPopup()
.saveScreenshot('./reports/screenshots/metamask_5.png')
.waitForElementPresent('[data-testid="page-container-footer-next"]', 60000)
.click('[data-testid="page-container-footer-next"]') // approve the tx
.switchBrowserTab(0) // back to remix
@ -199,11 +206,11 @@ const tests = {
*/
'Should debug Sepolia transaction with source highlighting MetaMask #group1': function (browser: NightwatchBrowser) {
if (!checkBrowserIsChrome(browser)) return
let txhash
browser.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
let txhash
browser.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('pluginManager') // load debugger and source verification
// .scrollAndClick('#pluginManager article[id="remixPluginManagerListItem_sourcify"] button')
// debugger already activated .scrollAndClick('#pluginManager article[id="remixPluginManagerListItem_debugger"] button')
// .scrollAndClick('#pluginManager article[id="remixPluginManagerListItem_sourcify"] button')
// debugger already activated .scrollAndClick('#pluginManager article[id="remixPluginManagerListItem_debugger"] button')
.clickLaunchIcon('udapp')
.perform((done) => {
browser.getLastTransactionHash((hash) => {
@ -213,15 +220,17 @@ const tests = {
})
.perform((done) => {
browser
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('debugger')
.setValue('*[data-id="debuggerTransactionInput"]', txhash) // debug tx
.click('*[data-id="debuggerTransactionStartButton"]')
.waitForElementVisible('*[data-id="treeViewDivto"]', 30000)
.checkVariableDebug('soliditylocals', localsCheck)
.perform(() => done())
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('debugger')
.setValue('*[data-id="debuggerTransactionInput"]', txhash) // debug tx
.saveScreenshot('./reports/screenshots/metamask_2.png')
.click('*[data-id="debuggerTransactionStartButton"]')
.saveScreenshot('./reports/screenshots/metamask_3.png')
.waitForElementVisible('*[data-id="treeViewDivto"]', 30000)
.checkVariableDebug('soliditylocals', localsCheck)
.perform(() => done())
})
},
'Call web3.eth.getAccounts() using Injected Provider (Metamask) #group1': function (browser: NightwatchBrowser) {
@ -229,14 +238,14 @@ const tests = {
browser
.executeScriptInTerminal('web3.eth.getAccounts()')
.journalLastChildIncludes('["0x76a3ABb5a12dcd603B52Ed22195dED17ee82708f"]')
}
}
}
const branch = process.env.CIRCLE_BRANCH;
const isMasterBranch = branch === 'master';
module.exports = {
...(branch ? (isMasterBranch ? tests : {}) : tests),
...{} //(branch ? (isMasterBranch ? tests : {}) : tests),
};
const localsCheck = {
@ -250,7 +259,7 @@ const sources = [
{
'Greet.sol': {
content:
`
`
pragma solidity ^0.8.0;
contract HelloWorld {
string public message;

@ -129,8 +129,7 @@ module.exports = {
.setValue('[data-id="modalDialogCustomPromptTextClone"]', 'https://github.com/yann300/remix-reward')
.click('[data-id="fileSystem-modal-footer-ok-react"]')
.waitForElementPresent('.fa-spinner')
.pause(5000)
.waitForElementNotPresent('.fa-spinner')
.waitForElementNotPresent('.fa-spinner', 120000)
.waitForElementVisible('*[data-id="treeViewLitreeViewItem.git"]')
.waitForElementContainsText('[data-id="workspacesSelect"]', 'remix-reward')
.clickLaunchIcon('solidity')

@ -180,16 +180,17 @@ module.exports = {
// creating a new workspace
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementPresent('*[data-id="create-remixDefault"]')
.scrollAndClick('*[data-id="create-remixDefault"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.click('*[data-id="fileSystemModalDialogContainer-react"] input[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="fileSystemModalDialogContainer-react"] input[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_new')
.click('input[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('input[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_new')
.pause(2000)
.getValue('*[data-id="fileSystemModalDialogContainer-react"] input[data-id="modalDialogCustomPromptTextCreate"]', (result) => {
.getValue('input[data-id="modalDialogCustomPromptTextCreate"]', (result) => {
console.log(result)
browser.assert.equal(result.value, 'workspace_new')
})
.waitForElementVisible('*[data-id="fileSystem-modal-footer-ok-react"]')
.click('*[data-id="fileSystem-modal-footer-ok-react"]')
.modalFooterOKClick('TemplatesSelection')
.pause(3000)
.currentWorkspaceIs('workspace_new')
.waitForElementVisible('li[data-id="treeViewLitreeViewItem.deps/remix-tests/remix_tests.sol"]')

@ -48,7 +48,7 @@ module.exports = {
// Check warning count
.waitForElementVisible('span#ssaRemixtab')
.click('span#ssaRemixtab')
.assert.containsText('span#ssaRemixtab > *[data-id="RemixStaticAnalysisErrorCount"]', '1')
.assert.containsText('span#ssaRemixtab > *[data-id="RemixStaticAnalysisErrorCount"]', '3')
.verify.elementPresent('input[name="showLibWarnings"]')
.verify.not.elementPresent('input[name="showLibWarnings"]:checked')
.verify.elementPresent('label[id="headingshowLibWarnings"]')
@ -56,10 +56,10 @@ module.exports = {
.pause(1000)
.waitForElementVisible('span#ssaRemixtab')
.click('span#ssaRemixtab')
.assert.containsText('span#ssaRemixtab > *[data-id="RemixStaticAnalysisErrorCount', '386')
.assert.containsText('span#ssaRemixtab > *[data-id="RemixStaticAnalysisErrorCount', '388')
.click('label[id="headingshowLibWarnings"]')
.pause(1000)
.assert.containsText('span#ssaRemixtab > *[data-id="RemixStaticAnalysisErrorCount', '1')
.assert.containsText('span#ssaRemixtab > *[data-id="RemixStaticAnalysisErrorCount', '3')
.end()
}
}

@ -313,6 +313,7 @@ module.exports = {
'Should connect to the sepolia fork and run web3.eth.getCode in the terminal #group9': function (browser: NightwatchBrowser) {
if (runMasterTests)
browser
.pinGrid('vm-custom-fork', true)
.switchEnvironment('vm-custom-fork')
.waitForElementVisible('[data-id="vm-custom-fork-modal-footer-ok-react"]')
.execute(() => {

@ -336,5 +336,18 @@ module.exports = {
.waitForElementVisible('*[data-shared="tooltipPopup"]')
.waitForElementContainsText('*[data-shared="tooltipPopup"]', 'initiating fileManager and calling "open" ...')
.waitForElementContainsText('*[data-shared="tooltipPopup"]', 'initiating terminal and calling "log" ...')
},
'Import Github folder from URL params #group4': function (browser: NightwatchBrowser) {
browser
.url('http://127.0.0.1:8080/#ghfolder=https://github.com/ethereum/remix-project/tree/master/apps/remix-ide/contracts/hardhat')
.refreshPage()
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]', 40000)
.currentWorkspaceIs('code-sample')
.openFile('contracts')
.openFile('contracts/Lock.sol')
.getEditorValue((content) => {
browser.assert.ok(content.indexOf('contract Lock {') !== -1, 'content does contain "contract Lock {"')
})
}
}

@ -38,12 +38,12 @@ module.exports = {
.clickLaunchIcon('filePanel')
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementPresent('*[data-id="create-remixDefault"]')
.scrollAndClick('*[data-id="create-remixDefault"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_remix_default' })
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_remix_default')
.modalFooterOKClick('TemplatesSelection')
.pause(1000)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/1_Storage.sol"]')
@ -110,14 +110,12 @@ module.exports = {
browser
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementPresent('*[data-id="create-blank"]')
.scrollAndClick('*[data-id="create-blank"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_blank' })
.click('select[id="wstemplate"]')
.click('select[id="wstemplate"] option[value=blank]')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_blank')
.modalFooterOKClick('TemplatesSelection')
.pause(100)
.waitForElementPresent('*[data-id="treeViewUltreeViewMenu"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItem.prettierrc.json"]')
@ -133,14 +131,12 @@ module.exports = {
browser
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementPresent('*[data-id="create-ozerc20"]')
.scrollAndClick('*[data-id="create-ozerc20"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_erc20' })
.click('select[id="wstemplate"]')
.click('select[id="wstemplate"] option[value=ozerc20]')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_erc20')
.modalFooterOKClick('TemplatesSelection')
.pause(100)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/MyToken.sol"]')
@ -194,14 +190,12 @@ module.exports = {
browser
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementPresent('*[data-id="create-ozerc721"]')
.scrollAndClick('*[data-id="create-ozerc721"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_erc721' })
.click('select[id="wstemplate"]')
.click('select[id="wstemplate"] option[value=ozerc721]')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_erc721')
.modalFooterOKClick('TemplatesSelection')
.pause(100)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/MyToken.sol"]')
@ -255,14 +249,12 @@ module.exports = {
browser
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementPresent('*[data-id="create-ozerc1155"]')
.scrollAndClick('*[data-id="create-ozerc1155"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_erc1155' })
.click('select[id="wstemplate"]')
.click('select[id="wstemplate"] option[value=ozerc1155]')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_erc1155')
.modalFooterOKClick('TemplatesSelection')
.pause(100)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/MyToken.sol"]')
@ -316,17 +308,10 @@ module.exports = {
browser
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementPresent(`*[data-id='create-ozerc1155{"upgradeable":"uups","mintable":true,"burnable":true,"pausable":true}']`)
.scrollAndClick(`*[data-id='create-ozerc1155{"upgradeable":"uups","mintable":true,"burnable":true,"pausable":true}']`)
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
.click('select[id="wstemplate"]')
.click('select[id="wstemplate"] option[value=ozerc1155]')
.waitForElementPresent('*[data-id="ozCustomization"]')
.click('*[data-id="featureTypeMintable"]')
.click('*[data-id="featureTypeBurnable"]')
.click('*[data-id="featureTypePausable"]')
.click('*[data-id="upgradeTypeUups"]')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.modalFooterOKClick('TemplatesSelection')
.pause(100)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/MyToken.sol"]')
@ -386,12 +371,10 @@ module.exports = {
browser
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementPresent('*[data-id="create-hashchecker"]')
.scrollAndClick('*[data-id="create-hashchecker"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
.click('select[id="wstemplate"]')
.click('select[id="wstemplate"] option[value=hashchecker]')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.modalFooterOKClick('TemplatesSelection')
.pause(100)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcircuits"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcircuits/calculate_hash.circom"]')
@ -424,11 +407,12 @@ module.exports = {
browser
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementPresent('*[data-id="create-remixDefault"]')
.scrollAndClick('*[data-id="create-remixDefault"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.click('*[data-id="fileSystemModalDialogContainer-react"] input[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="fileSystemModalDialogContainer-react"] input[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_name')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.click('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.click('input[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('input[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_name')
.modalFooterOKClick('TemplatesSelection')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtests"]')
.addFile('test.sol', { content: 'test' })
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtest.sol"]')
@ -438,11 +422,12 @@ module.exports = {
})
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementPresent('*[data-id="create-remixDefault"]')
.scrollAndClick('*[data-id="create-remixDefault"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.click('*[data-id="fileSystemModalDialogContainer-react"] input[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="fileSystemModalDialogContainer-react"] input[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_name_1')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.click('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.click('input[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('input[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_name_1')
.modalFooterOKClick('TemplatesSelection')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtests"]')
.waitForElementNotPresent('*[data-id="treeViewLitreeViewItemtest.sol"]')
.switchWorkspace('workspace_name')
@ -494,13 +479,12 @@ module.exports = {
.clickLaunchIcon('filePanel')
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementPresent('*[data-id="create-ozerc1155"]')
.scrollAndClick('*[data-id="create-ozerc1155"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
.click('select[id="wstemplate"]')
.click('select[id="wstemplate"] option[value=ozerc1155]')
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'sometestworkspace' })
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'sometestworkspace')
.modalFooterOKClick('TemplatesSelection')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/MyToken.sol"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItem.prettierrc.json"]')
@ -521,14 +505,12 @@ module.exports = {
browser
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementPresent('*[data-id="create-ozerc1155"]')
.scrollAndClick('*[data-id="create-ozerc1155"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
// eslint-disable-next-line dot-notation
.click('select[id="wstemplate"]')
.click('select[id="wstemplate"] option[value=ozerc1155]')
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_db_test' })
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_db_test')
.modalFooterOKClick('TemplatesSelection')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/MyToken.sol"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItem.prettierrc.json"]')
@ -551,14 +533,12 @@ module.exports = {
.clickLaunchIcon('filePanel')
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementPresent('*[data-id="create-uniswapV4HookBookMultiSigSwapHook"]')
.scrollAndClick('*[data-id="create-uniswapV4HookBookMultiSigSwapHook"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
.click('select[id="wstemplate"]')
.click('select[id="wstemplate"] option[value=uniswapV4HookBookMultiSigSwapHook]')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'multisig cookbook' })
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'multisig cookbook')
.modalFooterOKClick('TemplatesSelection')
.waitForElementVisible('[data-id="PermissionHandler-modal-footer-ok-react"]', 300000)
.click('[data-id="PermissionHandler-modal-footer-ok-react"]')
// click on lib to close it

@ -13,8 +13,9 @@ module.exports = {
.clickLaunchIcon('filePanel')
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementPresent('*[data-id="create-remixDefault"]')
.scrollAndClick('*[data-id="create-remixDefault"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
.waitForElementVisible({
selector: "//*[@class='text-warning' and contains(.,'add username and email')]",
locateStrategy: 'xpath'
@ -23,10 +24,10 @@ module.exports = {
selector: '//*[@data-id="initGitRepository"][@disabled]',
locateStrategy: 'xpath'
})
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_blank' })
.scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_blank')
.click('[data-id="initGitRepositoryLabel"]')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.modalFooterOKClick('TemplatesSelection')
.pause(100)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]')
.waitForElementNotPresent('*[data-id="treeViewLitreeViewItem.git"]')
@ -47,15 +48,13 @@ module.exports = {
.waitForElementNotVisible('[data-id="workspaceGitPanel"]')
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementPresent('*[data-id="create-blank"]')
.scrollAndClick('*[data-id="create-blank"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_blank' })
.click('select[id="wstemplate"]')
.click('select[id="wstemplate"] option[value=blank]')
.scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_blank')
.click('[data-id="initGitRepositoryLabel"]')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.modalFooterOKClick('TemplatesSelection')
.pause(100)
.waitForElementVisible('[data-id="workspaceGitPanel"]')
.waitForElementContainsText('[data-id="workspaceGitBranchesDropdown"]', 'main')
@ -391,12 +390,10 @@ module.exports = {
browser
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementPresent('*[data-id="create-uniswapV4Template"]')
.scrollAndClick('*[data-id="create-uniswapV4Template"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
.click('select[id="wstemplate"]')
.click('select[id="wstemplate"] option[value=uniswapV4Template]')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.modalFooterOKClick('TemplatesSelection')
.pause(100)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemsrc"]')
.openFile('src')
@ -413,14 +410,12 @@ module.exports = {
.clickLaunchIcon('filePanel')
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementPresent('*[data-id="create-ozerc20"]')
.scrollAndClick('*[data-id="create-ozerc20"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
// eslint-disable-next-line dot-notation
.click('select[id="wstemplate"]')
.click('select[id="wstemplate"] option[value=ozerc20]')
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'new_workspace' })
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]')
.setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'new_workspace')
.modalFooterOKClick('TemplatesSelection')
.waitForElementVisible('*[data-id="treeViewDivDraggableItemtests/MyToken_test.sol"]')
},
'Update settings for git #group5': function (browser: NightwatchBrowser) {
@ -468,13 +463,11 @@ module.exports = {
.clickLaunchIcon('filePanel')
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementPresent('*[data-id="create-uniswapV4Template"]')
.scrollAndClick('*[data-id="create-uniswapV4Template"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
.click('select[id="wstemplate"]')
.click('select[id="wstemplate"] option[value=uniswapV4Template]')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.pause(100)
.modalFooterOKClick('TemplatesSelection')
.pause(100)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemsrc"]')
.openFile('src')
.openFile('src/Counter.sol')
@ -491,11 +484,11 @@ module.exports = {
.click('*[data-id="remotes-panel"]')
.waitForElementVisible('*[data-id="remotes-panel-content"]')
.click({
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-detail-origin"]',
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-detail-origin-default"]',
locateStrategy: 'xpath'
})
.waitForElementVisible({
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-detail-origin" and contains(.,"v4-template")]',
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-detail-origin-default" and contains(.,"v4-template")]',
locateStrategy: 'xpath'
})
},

@ -48,6 +48,7 @@ declare module 'nightwatch' {
removeFile(path: string, workspace: string): NightwatchBrowser
switchBrowserWindow(url: string, windowName: string, cb: (browser: NightwatchBrowser, window?: NightwatchCallbackResult<Window>) => void): NightwatchBrowser
setupMetamask(passphrase: string, password: string): NightwatchBrowser
hideMetaMaskPopup(): NightwatchBrowser
signMessage(msg: string, callback: (hash: {value: string}, signature: {value: string}) => void): NightwatchBrowser
setSolidityCompilerVersion(version: string): NightwatchBrowser
clickElementAtPosition(cssSelector: string, index: number, opt?: {forceSelectIfUnselected: boolean}): NightwatchBrowser
@ -69,6 +70,7 @@ declare module 'nightwatch' {
currentSelectedFileIs(name: string): NightwatchBrowser
switchWorkspace: (workspaceName: string) => NightwatchBrowser
switchEnvironment: (provider: string) => NightwatchBrowser
pinGrid: (provider: string, status: boolean) => NightwatchBrowser
connectToExternalHttpProvider: (url: string, identifier: string) => NightwatchBrowser
waitForElementNotContainsText: (id: string, value: string, timeout: number = 10000) => NightwatchBrowser
hideToolTips: (this: NightwatchBrowser) => NightwatchBrowser

@ -3,6 +3,11 @@
set -e
SHA=`git rev-parse --short --verify HEAD`
# this gh action is used to deploy the build to the gh pages
mkdir dist/apps/remix-ide/.github
mkdir dist/apps/remix-ide/.github/workflows
cp apps/remix-ide/ci/gh-actions-deploy.yml dist/apps/remix-ide/.github/workflows
cd dist/apps/remix-ide
git init

@ -3,6 +3,11 @@
set -e
SHA=`git rev-parse --short --verify HEAD`
# this gh action is used to deploy the build to the gh pages
mkdir dist/apps/remix-ide/.github
mkdir dist/apps/remix-ide/.github/workflows
cp apps/remix-ide/ci/gh-actions-deploy.yml dist/apps/remix-ide/.github/workflows
cd dist/apps/remix-ide
git init

@ -3,6 +3,11 @@
set -e
SHA=`git rev-parse --short --verify HEAD`
# this gh action is used to deploy the build to the gh pages
mkdir dist/apps/remix-ide/.github
mkdir dist/apps/remix-ide/.github/workflows
cp apps/remix-ide/ci/gh-actions-deploy.yml dist/apps/remix-ide/.github/workflows/gh-actions-deploy.yml
cd dist/apps/remix-ide
git init

@ -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

@ -40,6 +40,7 @@ import {HardhatProvider} from './app/providers/hardhat-provider'
import {GanacheProvider} from './app/providers/ganache-provider'
import {FoundryProvider} from './app/providers/foundry-provider'
import {ExternalHttpProvider} from './app/providers/external-http-provider'
import { EnvironmentExplorer } from './app/providers/environment-explorer'
import { FileDecorator } from './app/plugins/file-decorator'
import { CodeFormat } from './app/plugins/code-format'
import { SolidityUmlGen } from './app/plugins/solidity-umlgen'
@ -60,6 +61,8 @@ import { Matomo } from './app/plugins/matomo'
import {SolCoder} from './app/plugins/solcoderAI'
import { TemplatesSelectionPlugin } from './app/plugins/templates-selection/templates-selection-plugin'
const isElectron = require('is-electron')
const remixLib = require('@remix-project/remix-lib')
@ -276,6 +279,8 @@ class AppComponent {
const ganacheProvider = new GanacheProvider(blockchain)
const foundryProvider = new FoundryProvider(blockchain)
const externalHttpProvider = new ExternalHttpProvider(blockchain)
const environmentExplorer = new EnvironmentExplorer()
// ----------------- convert offset to line/column service -----------
const offsetToLineColumnConverter = new OffsetToLineColumnConverter()
Registry.getInstance().put({
@ -312,6 +317,8 @@ class AppComponent {
// ----------------- run script after each compilation results -----------
const pluginStateLogger = new PluginStateLogger()
const templateSelection = new TemplatesSelectionPlugin()
this.engine.register([
permissionHandler,
this.layout,
@ -350,6 +357,7 @@ class AppComponent {
ganacheProvider,
foundryProvider,
externalHttpProvider,
environmentExplorer,
this.walkthroughService,
search,
solidityumlgen,
@ -362,7 +370,8 @@ class AppComponent {
solcoder,
git,
pluginStateLogger,
matomo
matomo,
templateSelection
])
//---- fs plugin
@ -505,7 +514,7 @@ class AppComponent {
])
await this.appManager.activatePlugin(['settings'])
await this.appManager.activatePlugin(['walkthrough', 'storage', 'search', 'compileAndRun', 'recorder', 'dgit'])
await this.appManager.activatePlugin(['walkthrough', 'storage', 'search', 'compileAndRun', 'recorder', 'dgitApi', 'dgit'])
await this.appManager.activatePlugin(['solidity-script', 'remix-templates'])
if (isElectron()){

@ -348,7 +348,6 @@ class Editor extends Plugin {
}
async openDiff(change) {
console.log('openDiff', change)
const hashedPathModified = change.readonly ? change.path + change.hashModified : change.path
const hashedPathOriginal = change.path + change.hashOriginal
const session = await this._createSession(hashedPathModified, change.modified, this._getMode(change.path), change.readonly)
@ -458,7 +457,6 @@ class Editor extends Plugin {
revealRange (startLineNumber, startColumn, endLineNumber, endColumn) {
if (!this.activated) return
this.emit('focus')
console.log(startLineNumber, startColumn, endLineNumber, endColumn)
this.emit('revealRange', startLineNumber, startColumn, endLineNumber, endColumn)
}

@ -312,7 +312,7 @@ class FileManager extends Plugin {
await this._handleExists(dest, `Cannot paste content into ${dest}. Path does not exist.`)
await this._handleIsDir(dest, `Cannot paste content into ${dest}. Path is not directory.`)
const content = await this.readFile(src)
let copiedFilePath = dest + (customName ? '/' + customName : '/' + `Copy_${helper.extractNameFromKey(src)}`)
let copiedFilePath = dest + (customName ? '/' + customName : '/' + `${helper.extractNameFromKey(src)}`)
copiedFilePath = await helper.createNonClashingNameAsync(copiedFilePath, this)
await this.writeFile(copiedFilePath, content)
@ -962,6 +962,12 @@ class FileManager extends Plugin {
return exists
}
/**
* Check if a file can be moved
* @param src source file
* @param dest destination file
* @returns {boolean} true if the file is allowed to be moved
*/
async moveFileIsAllowed (src: string, dest: string) {
try {
src = this.normalize(src)
@ -984,6 +990,12 @@ class FileManager extends Plugin {
}
}
/**
* Check if a folder can be moved
* @param src source folder
* @param dest destination folder
* @returns {boolean} true if the folder is allowed to be moved
*/
async moveDirIsAllowed (src: string, dest: string) {
try {
src = this.normalize(src)

@ -195,7 +195,7 @@ module.exports = class Filepanel extends ViewPlugin {
if (err) reject(err)
else resolve(data || true)
})
})
}, false)
}
renameWorkspace(oldName, workspaceName) {

@ -33,7 +33,9 @@ export class Layout extends Plugin {
maximised: { [key: string]: boolean }
constructor () {
super(profile)
this.maximised = {}
this.maximised = {
'dgit': true
}
this.event = new EventEmitter()
}
@ -65,7 +67,6 @@ export class Layout extends Plugin {
this.event.emit('change', null)
})
this.on('tabs', 'openDiff', () => {
console.log('openDiff')
this.panels.editor.active = true
this.panels.main.active = false
this.event.emit('change', null)
@ -126,15 +127,15 @@ export class Layout extends Plugin {
}
async maximiseSidePanel () {
this.event.emit('maximisesidepanel')
const current = await this.call('sidePanel', 'currentFocus')
this.maximised[current] = true
this.event.emit('maximisesidepanel')
}
async maximisePinnedPanel () {
this.event.emit('maximisepinnedpanel')
const current = await this.call('pinnedPanel', 'currentFocus')
this.maximised[current] = true
this.event.emit('maximisepinnedpanel')
}
async maximizeTerminal() {
@ -144,14 +145,14 @@ export class Layout extends Plugin {
}
async resetSidePanel () {
this.event.emit('resetsidepanel')
const current = await this.call('sidePanel', 'currentFocus')
this.maximised[current] = false
this.event.emit('resetsidepanel')
}
async resetPinnedPanel () {
this.event.emit('resetpinnedpanel')
const current = await this.call('pinnedPanel', 'currentFocus')
this.maximised[current] = false
this.event.emit('resetpinnedpanel')
}
}

@ -1,11 +1,12 @@
'use strict'
import { ViewPlugin } from '@remixproject/engine-web';
import { ViewPlugin } from '@remixproject/engine-web'
import React from 'react' // eslint-disable-line
import { gitState, GitUI } from '@remix-ui/git';
import { gitState, GitUI } from '@remix-ui/git'
import * as packageJson from '../../../../../package.json'
const profile = {
name: 'dgit',
displayName: 'Git',
desciption: 'Git plugin for Remix',
methods: ['pull', 'track', 'diff', 'clone', 'open'],
events: [''],

@ -11,7 +11,7 @@ const profile = {
version: '1.0.0'
}
const allowedPlugins = ['LearnEth', 'etherscan', 'vyper', 'circuit-compiler', 'doc-gen', 'doc-viewer', 'solhint', 'walletconnect', 'scriptRunner']
const allowedPlugins = ['LearnEth', 'etherscan', 'vyper', 'circuit-compiler', 'doc-gen', 'doc-viewer', 'solhint', 'walletconnect', 'scriptRunner', 'dgit']
export class Matomo extends Plugin {

@ -123,6 +123,8 @@ export class RemixGuidePlugin extends ViewPlugin {
expandViewEl={
cell.expandViewElement
}
key={cell.title}
id={cell.title}
handleExpand={() => {
this.showVideo = true
this.videoID = cell.expandViewElement.videoID

@ -43,6 +43,7 @@ export class RemixdHandle extends WebsocketPlugin {
await this.appManager.deactivatePlugin(plugin)
}
}
//@ts-ignore
if (super.socket) super.deactivate()
// this.appManager.deactivatePlugin('git') // plugin call doesn't work.. see issue https://github.com/ethereum/remix-plugin/issues/342
this.localhostProvider.close((error) => {

@ -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;
}

@ -10,6 +10,16 @@
"circuit.noFileSelected": "no file selected",
"circuit.generateR1cs": "Generate R1CS",
"circuit.computeWitness": "Compute Witness",
"circuit.generateProof": "Generate Proof",
"circuit.signalInput": "Signal Input",
"circuit.compute": "Compute"
"circuit.compute": "Compute",
"circuit.setupExports": "Setup and Exports",
"circuit.provingScheme": "Proving Scheme",
"circuit.ptau": "POWER OF TAU (PTAU)",
"circuit.randomText": "Ceremony: Random Text",
"circuit.randomBeacon": "Ceremony: Random Beacon",
"circuit.exportVerifierContract": "Export verifier contract",
"circuit.exportVerificationKey": "Export verification key",
"circuit.exportVerifierCalldata": "Export verifier calldata",
"circuit.runSetup": "Run setup"
}

@ -5,7 +5,7 @@
"home.scamAlertText2": "Beware of online videos promoting \"liquidity front runner bots\"",
"home.scamAlertText3": "Additional safety tips",
"home.learnMore": "Learn more",
"home.here": "here",
"home.here": "more",
"home.featured": "Featured",
"home.jumpIntoWeb3": "JUMP INTO WEB3",
"home.jumpIntoWeb3More": "More",

@ -39,6 +39,7 @@
"terminal.executionCost": "execution cost",
"terminal.input": "input",
"terminal.decodedInput": "decoded input",
"terminal.output": "output",
"terminal.decodedOutput": "decoded output",
"terminal.rawlogs": "raw logs",
"terminal.logs": "logs"

@ -38,6 +38,7 @@
"terminal.executionCost": "costo de ejecución",
"terminal.input": "entrada",
"terminal.decodedInput": "entrada descodificada",
"terminal.output": "salida",
"terminal.decodedOutput": "salida descodificada",
"terminal.rawlogs": "registros sin procesar",
"terminal.logs": "registros"

@ -38,6 +38,7 @@
"terminal.executionCost": "coût d'exécution",
"terminal.input": "entrée",
"terminal.decodedInput": "entrée décodée",
"terminal.output": "sortie",
"terminal.decodedOutput": "sortie décodée",
"terminal.rawlogs": "logs bruts",
"terminal.logs": "Log"

@ -38,6 +38,7 @@
"terminal.executionCost": "실행 비용",
"terminal.input": "입력",
"terminal.decodedInput": "디코드된 입력",
"terminal.output": "출력",
"terminal.decodedOutput": "디코드된 출력",
"terminal.rawlogs": "원시 로그",
"terminal.logs": "로그"

@ -38,6 +38,7 @@
"terminal.executionCost": "стоимость исполнения",
"terminal.input": "ввод",
"terminal.decodedInput": "декодированный ввод",
"terminal.output": "вывод",
"terminal.decodedOutput": "декодированный вывод",
"terminal.rawlogs": "необработанные журналы",
"terminal.logs": "журналы"

@ -38,6 +38,7 @@
"terminal.executionCost": "执行成本",
"terminal.input": "输入",
"terminal.decodedInput": "解码输入",
"terminal.output": "输出",
"terminal.decodedOutput": "解码输出",
"terminal.rawlogs": "原始日志",
"terminal.logs": "日志"

@ -129,6 +129,42 @@ export class RunTab extends ViewPlugin {
async onInitDone() {
const udapp = this // eslint-disable-line
const descriptions = {
'vm-cancun': 'Deploy to the in-browser virtual machine running the Cancun fork.',
'vm-shanghai': 'Deploy to the in-browser virtual machine running the Shanghai fork.',
'vm-paris': 'Deploy to the in-browser virtual machine running the Paris fork.',
'vm-london': 'Deploy to the in-browser virtual machine running the London fork.',
'vm-berlin': 'Deploy to the in-browser virtual machine running the Berlin fork.',
'vm-mainnet-fork': 'Deploy to a fork of the Ethereum mainnet in the in-browser virtual machine.',
'vm-sepolia-fork': 'Deploy to a fork of the Sepolia testnet in the in-browser virtual machine.',
'vm-custom-fork': 'Deploy to a fork of a custom network in the in-browser virtual machine.',
'walletconnect': 'Deploy using WalletConnect.',
'basic-http-provider': 'Deploy to a Custom local network.',
'hardhat-provider': 'Deploy to the local Hardhat dev chain.',
'ganache-provider': 'Deploy to the local Ganache dev chain.',
'foundry-provider': 'Deploy to the local Foundry dev chain.',
'injected-MetaMask': 'Deploy through the Metamask browser extension.',
'injected-Brave Wallet': 'Deploy through the Brave Wallet extension.',
'injected-Brave': 'Deploy through the Brave browser extension.',
'injected-metamask-optimism': 'Deploy to Optimism through the Metamask browser extension.',
'injected-metamask-arbitrum': 'Deploy to Arbitrum through the Metamask browser extension.',
'injected-metamask-sepolia': 'Deploy to the Sepolia testnet through the Metamask browser extension.',
'injected-metamask-ephemery': 'Deploy to the Ephemery testnet through the Metamask browser extension.'
}
const logos = {
'injected-metamask-optimism': ['assets/img/optimism-ethereum-op-logo.png', 'assets/img/metamask.png'],
'injected-metamask-arbitrum': ['assets/img/arbitrum-arb-logo.png', 'assets/img/metamask.png'],
'injected-metamask-sepolia': ['assets/img/metamask.png'],
'injected-metamask-ephemery': ['assets/img/metamask.png'],
'injected-MetaMask': ['assets/img/metamask.png'],
'injected-Brave Wallet': ['assets/img/brave.png'],
'injected-Trust Wallet': ['assets/img/trust-wallet.png'],
'hardhat-provider': ['assets/img/hardhat.png'],
'walletconnect': ['assets/img/Walletconnect-logo.png'],
'foundry-provider': ['assets/img/foundry.png']
}
const addProvider = async (position, name, displayName, isInjected, isVM, fork = '', dataId = '', title = '') => {
await this.call('blockchain', 'addProvider', {
position,
@ -136,6 +172,8 @@ export class RunTab extends ViewPlugin {
dataId,
name,
displayName,
description: descriptions[name] || displayName,
logos: logos[name],
fork,
isInjected,
isVM,
@ -167,15 +205,15 @@ export class RunTab extends ViewPlugin {
await addProvider(0, name, displayName, true, false, false)
if (event.detail.info.name === 'MetaMask') {
await addCustomInjectedProvider(7, event, 'injected-metamask-optimism', 'L2 - Optimism', '0xa', ['https://mainnet.optimism.io'])
await addCustomInjectedProvider(8, event, 'injected-metamask-arbitrum', 'L2 - Arbitrum', '0xa4b1', ['https://arb1.arbitrum.io/rpc'])
await addCustomInjectedProvider(5, event, 'injected-metamask-sepolia', 'Testnet - Sepolia', '0xaa36a7', [],
await addCustomInjectedProvider(7, event, 'injected-metamask-optimism', 'L2 - Optimism - ' + event.detail.info.name, '0xa', ['https://mainnet.optimism.io'])
await addCustomInjectedProvider(8, event, 'injected-metamask-arbitrum', 'L2 - Arbitrum - ' + event.detail.info.name, '0xa4b1', ['https://arb1.arbitrum.io/rpc'])
await addCustomInjectedProvider(5, event, 'injected-metamask-sepolia', 'Sepolia Testnet - ' + event.detail.info.name, '0xaa36a7', [],
{
"name": "Sepolia ETH",
"symbol": "ETH",
"decimals": 18
})
await addCustomInjectedProvider(9, event, 'injected-metamask-ephemery', 'Ephemery Testnet', '', ['https://otter.bordel.wtf/erigon', 'https://eth.ephemeral.zeus.fyi'],
await addCustomInjectedProvider(9, event, 'injected-metamask-ephemery', 'Ephemery Testnet - ' + event.detail.info.name, '', ['https://otter.bordel.wtf/erigon', 'https://eth.ephemeral.zeus.fyi'],
{
"name": "Ephemery ETH",
"symbol": "ETH",
@ -192,7 +230,7 @@ export class RunTab extends ViewPlugin {
}
}
// VM
// VM
const titleVM = 'Execution environment is local to Remix. Data is only saved to browser memory and will vanish upon reload.'
await addProvider(1, 'vm-cancun', 'Remix VM (Cancun)', false, true, 'cancun', 'settingsVMCancunMode', titleVM)
await addProvider(50, 'vm-shanghai', 'Remix VM (Shanghai)', false, true, 'shanghai', 'settingsVMShanghaiMode', titleVM)

@ -2507,12 +2507,12 @@ fieldset:disabled a.btn {
}
.btn-warning {
color: #915900;
background-color: #fbdf9f;
border-color: #fbdf9f;
color: #ffffff;
background-color: #f1b513;
border-color: #f1b513;
}
.btn-warning:hover {
color: #915900;
color: #ffffff;
background-color: #ad632f;
border-color: #a35e2c;
}
@ -2523,15 +2523,15 @@ fieldset:disabled a.btn {
.btn-warning.disabled,
.btn-warning:disabled {
color: #fff;
background-color: #fbdf9f;
border-color: #fbdf9f;
background-color: #dabb68;
border-color: #dabb68;
}
.btn-warning:not(:disabled):not(.disabled):active,
.btn-warning:not(:disabled):not(.disabled).active,
.show > .btn-warning.dropdown-toggle {
color: #fff;
background-color: #a35e2c;
border-color: #99582a;
background-color: #dabb68;
border-color: #dabb68;
}
.btn-warning:not(:disabled):not(.disabled):active:focus,
.btn-warning:not(:disabled):not(.disabled).active:focus,
@ -2766,13 +2766,13 @@ fieldset:disabled a.btn {
}
.btn-outline-warning {
color: #fbdf9f;
border-color: #fbdf9f;
color: #f1b513;
border-color: #f1b513;
}
.btn-outline-warning:hover {
color: #fff;
background-color: #fbdf9f;
border-color: #fbdf9f;
background-color: #f1b513;
border-color: #f1b513;
}
.btn-outline-warning:focus,
.btn-outline-warning.focus {
@ -2780,15 +2780,15 @@ fieldset:disabled a.btn {
}
.btn-outline-warning.disabled,
.btn-outline-warning:disabled {
color: #fbdf9f;
color: #f1b513;
background-color: transparent;
}
.btn-outline-warning:not(:disabled):not(.disabled):active,
.btn-outline-warning:not(:disabled):not(.disabled).active,
.show > .btn-outline-warning.dropdown-toggle {
color: #fff;
background-color: #fbdf9f;
border-color: #fbdf9f;
background-color: #f1b513;
border-color: #f1b513;
}
.btn-outline-warning:not(:disabled):not(.disabled):active:focus,
.btn-outline-warning:not(:disabled):not(.disabled).active:focus,
@ -4793,7 +4793,7 @@ a.badge-info.focus {
.badge-warning {
color: #b1270f;
background-color: #fbdf9f;
background-color: #f1b513;
}
a.badge-warning:hover,
a.badge-warning:focus {
@ -6188,7 +6188,7 @@ button.bg-info:focus {
}
.bg-warning {
background-color: #fbdf9f !important;
background-color: #f1b513 !important;
}
a.bg-warning:hover,
@ -6300,7 +6300,7 @@ button.bg-dark:focus {
}
.border-warning {
border-color: #fbdf9f !important;
border-color: #f1b513 !important;
}
.border-danger {

@ -6190,7 +6190,7 @@ button.bg-info:focus {
}
.bg-warning {
background-color: #edc464 !important;
background-color: #bc8401 !important;
}
a.bg-warning:hover,

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

@ -23,7 +23,7 @@ const profile = {
name: 'blockchain',
displayName: 'Blockchain',
description: 'Blockchain - Logic',
methods: ['getCode', 'getTransactionReceipt', 'addProvider', 'removeProvider', 'getCurrentFork', 'getAccounts', 'web3VM', 'web3', 'getProvider', 'getCurrentNetworkStatus'],
methods: ['getCode', 'getTransactionReceipt', 'addProvider', 'removeProvider', 'getCurrentFork', 'getAccounts', 'web3VM', 'web3', 'getProvider', 'getCurrentNetworkStatus', 'getAllProviders', 'getPinnedProviders'],
version: packageJson.version
}
@ -44,6 +44,24 @@ export type Transaction = {
timestamp?: number
}
export type Provider = {
options: { [key: string]: string }
dataId: string
name: string
displayName: string
logo?: string,
logos?: string[],
fork: string
description?: string
isInjected: boolean
isVM: boolean
title: string
init: () => Promise<void>
provider:{
sendAsync: (payload: any) => Promise<void>
}
}
export class Blockchain extends Plugin {
active: boolean
event: EventManager
@ -62,6 +80,7 @@ export class Blockchain extends Plugin {
providers: {[key: string]: VMProvider | InjectedProvider | NodeProvider}
transactionContextAPI: TransactionContextAPI
registeredPluginEvents: string[]
pinnedProviders: string[]
// NOTE: the config object will need to be refactored out in remix-lib
constructor(config: Config) {
@ -93,6 +112,7 @@ export class Blockchain extends Plugin {
this.networkcallid = 0
this.networkStatus = { network: { name: ' - ', id: ' - ' } }
this.registeredPluginEvents = []
this.pinnedProviders = ['vm-cancun', 'vm-shanghai', 'vm-mainnet-fork', 'vm-london', 'vm-berlin', 'vm-paris', 'walletconnect', 'injected-MetaMask', 'basic-http-provider', 'ganache-provider', 'hardhat-provider', 'foundry-provider']
this.setupEvents()
this.setupProviders()
}
@ -116,6 +136,14 @@ export class Blockchain extends Plugin {
})
}
})
this.on('environmentExplorer', 'providerPinned', (name, provider) => {
this.emit('shouldAddProvidertoUdapp', name, provider)
})
this.on('environmentExplorer', 'providerUnpinned', (name, provider) => {
this.emit('shouldRemoveProviderFromUdapp', name, provider)
})
}
onDeactivation() {
@ -136,12 +164,12 @@ export class Blockchain extends Plugin {
})
})
this.executionContext.event.register('addProvider', (network) => {
this._triggerEvent('addProvider', [network])
this.executionContext.event.register('providerAdded', (network) => {
this._triggerEvent('providerAdded', [network])
})
this.executionContext.event.register('removeProvider', (name) => {
this._triggerEvent('removeProvider', [name])
this.executionContext.event.register('providerRemoved', (name) => {
this._triggerEvent('providerRemoved', [name])
})
setInterval(() => {
@ -504,7 +532,11 @@ export class Blockchain extends Plugin {
}
changeExecutionContext(context, confirmCb, infoCb, cb) {
return this.executionContext.executionContextChange(context, null, confirmCb, infoCb, cb)
if (context.context === 'item-another-chain') {
this.call('manager', 'activatePlugin', 'environmentExplorer').then(() => this.call('tabs', 'focus', 'environmentExplorer'))
} else {
return this.executionContext.executionContextChange(context, null, confirmCb, infoCb, cb)
}
}
detectNetwork(cb) {
@ -611,7 +643,8 @@ export class Blockchain extends Plugin {
this.executionContext.listenOnLastBlock()
}
addProvider(provider) {
addProvider(provider: Provider) {
if (this.pinnedProviders.includes(provider.name)) this.emit('shouldAddProvidertoUdapp', provider.name, provider)
this.executionContext.addProvider(provider)
}
@ -619,6 +652,14 @@ export class Blockchain extends Plugin {
this.executionContext.removeProvider(name)
}
getAllProviders() {
return this.executionContext.getAllProviders()
}
getPinnedProviders() {
return this.pinnedProviders
}
// TODO : event should be triggered by Udapp instead of TxListener
/** Listen on New Transaction. (Cannot be done inside constructor because txlistener doesn't exist yet) */
startListening(txlistener) {

@ -123,10 +123,13 @@ export class ExecutionContext {
addProvider (network) {
if (network && network.name && !this.customNetWorks[network.name]) {
this.customNetWorks[network.name] = network
this.event.trigger('addProvider', [network])
}
}
getAllProviders () {
return this.customNetWorks
}
internalWeb3 () {
return web3
}

@ -18,9 +18,10 @@ export default {
},
shortenHexData: function (data) {
if (!data) return ''
if (data.length < 5) return data
var sliceLen = 5
var len = data.length
return data.slice(0, 5) + '...' + data.slice(len - 5, len)
if (len < sliceLen * 2) return data
return data.slice(0, sliceLen) + '...' + data.slice(len - sliceLen, len)
},
createNonClashingNameWithPrefix (name, fileProvider, prefix, cb) {
if (!name) name = 'Undefined'

@ -82,11 +82,12 @@ let requiredModules = [ // services + layout views + system views
'pinnedPanel',
'pluginStateLogger',
'remixGuide',
'matomo'
'environmentExplorer',
'templateSelection',
'matomo',
'walletconnect'
]
// dependentModules shouldn't be manually activated (e.g hardhat is activated by remixd)
const dependentModules = ['foundry', 'hardhat', 'truffle', 'slither']
@ -133,7 +134,9 @@ export function isNative(name) {
'circuit-compiler',
'compilationDetails',
'vyperCompilationDetails',
'remixGuide',
//'remixGuide',
'environmentExplorer',
'templateSelection',
'walletconnect'
]
return nativePlugins.includes(name) || requiredModules.includes(name) || isInjectedProvider(name) || isVM(name)
@ -389,7 +392,16 @@ class PluginLoader {
constructor() {
const queryParams = new QueryParams()
this.donotAutoReload = ['remixd'] // that would be a bad practice to force loading some plugins at page load.
// some plugins should not be activated at page load.
this.donotAutoReload = [
'remixd',
'environmentExplorer',
'templateSelection',
'compilationDetails',
'walletconnect',
'dapp-draft',
'solidityumlgen'
]
this.loaders = {}
this.loaders.localStorage = {
set: (plugin, actives) => {

@ -28,6 +28,8 @@ export class RemixEngine extends Engine {
if (name === 'fileManager') return { queueTimeout: 60000 * 20 }
if (name === 'solcoder') return { queueTimeout: 60000 * 2 }
if (name === 'cookbookdev') return { queueTimeout: 60000 * 3 }
if (name === 'contentImport') return { queueTimeout: 60000 * 3 }
return { queueTimeout: 10000 }
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save