Skip to content

Commit f1c7c6d

Browse files
CoveMBericglau
andauthored
Remix encoding fix (#665)
Co-authored-by: Eric Lau <[email protected]>
1 parent dea25c8 commit f1c7c6d

File tree

9 files changed

+119
-9
lines changed

9 files changed

+119
-9
lines changed

.github/workflows/test.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,16 @@ jobs:
4040
run: yarn test
4141
working-directory: packages/mcp
4242

43+
ui:
44+
runs-on: ubuntu-latest
45+
steps:
46+
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
47+
- name: Set up environment
48+
uses: ./.github/actions/setup
49+
- name: Run tests
50+
run: yarn test
51+
working-directory: packages/ui
52+
4353
build:
4454
name: build (${{ matrix.package }}, ${{ matrix.variant }})
4555
timeout-minutes: 90

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"lint": "eslint",
88
"format:write": "prettier --write \"**/*\"",
99
"format:check": "prettier --check \"**/*\"",
10+
"test:ui": "yarn --cwd ./packages/ui test",
1011
"type:check:api": "yarn --cwd ./packages/ui type:check:api",
1112
"dev:ui": "yarn --cwd ./packages/ui dev",
1213
"dev:api": "yarn --cwd ./packages/ui dev:api",
@@ -41,4 +42,4 @@
4142
"@changesets/cli": "^2.29.2",
4243
"@changesets/changelog-github": "^0.5.1"
4344
}
44-
}
45+
}

packages/ui/ava.config.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module.exports = {
2+
extensions: ['ts'],
3+
require: ['./ts-node-register.cjs'],
4+
timeout: '10m',
5+
workerThreads: false,
6+
};

packages/ui/package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@
99
"dev": "node scripts/copy-versions.mjs && rollup -c -w",
1010
"dev:api": "ENV=dev deno task dev",
1111
"start": "sirv public",
12-
"validate": "svelte-check"
12+
"validate": "svelte-check",
13+
"test": "ava"
1314
},
1415
"devDependencies": {
16+
"ava": "^6.1.2",
1517
"@rollup/plugin-alias": "^5.0.0",
1618
"@rollup/plugin-commonjs": "^28.0.0",
1719
"@rollup/plugin-json": "^6.0.0",
@@ -38,6 +40,7 @@
3840
"svelte-preprocess": "^5.0.0",
3941
"tailwindcss": "^3.0.15",
4042
"tslib": "^2.0.0",
43+
"ts-node": "^10.9.2",
4144
"typescript": "^5.0.0"
4245
},
4346
"dependencies": {
@@ -50,4 +53,4 @@
5053
"tippy.js": "^6.3.1",
5154
"util": "^0.12.4"
5255
}
53-
}
56+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import test from 'ava';
2+
import { remixURL } from './remix';
3+
4+
// Decoder used in Remix
5+
const decodeBase64 = (b64Payload: string) => {
6+
const raw = atob(decodeURIComponent(b64Payload));
7+
const bytes = Uint8Array.from(raw, c => c.charCodeAt(0));
8+
return new TextDecoder().decode(bytes);
9+
};
10+
11+
test('remixURL encodes code param decodable by decodeBase64', t => {
12+
const contractSource = 'contract A{}';
13+
14+
const url = remixURL(contractSource);
15+
const codeParam = url.searchParams.get('code');
16+
t.truthy(codeParam, 'Expected code search param to be set');
17+
18+
const decoded = decodeBase64(codeParam!);
19+
t.is(decoded, contractSource, 'Decoded code should equal original source');
20+
});
21+
22+
test('remixURL sets deployProxy flag when upgradeable', t => {
23+
const contractSource = 'contract A{}';
24+
25+
const urlTrue = remixURL(contractSource, true);
26+
t.is(urlTrue.searchParams.get('deployProxy'), 'true');
27+
28+
const urlFalse = remixURL(contractSource, false);
29+
t.is(urlFalse.searchParams.get('deployProxy'), null);
30+
});
31+
32+
test('remixURL encodes code param with special characters decodable by decodeBase64', t => {
33+
// not a valid contract
34+
const contractSource = `// SPDX-License-Identifier: MIT
35+
// Compatible with OpenZeppelin Contracts ^5.4.0
36+
pragma solidity ^0.8.27;
37+
38+
import {AccountERC7579} from "@openzeppelin/contracts/account/extensions/draft-AccountERC7579.sol";
39+
import {ERC7739} from "@openzeppelin/contracts/utils/cryptography/signers/draft-ERC7739.sol";
40+
41+
contract MyAccount is ERC7739, AccountERC7579 {
42+
constructor(address signer)
43+
EIP712(unicode"MyAccount🌾", "1")
44+
SignerECDSA(signer)
45+
{}
46+
47+
function isValidSignature(bytes32 hash, bytes calldata signature)
48+
public
49+
view
50+
override(AccountERC7579, ERC7739)
51+
returns (bytes4)
52+
{
53+
// ERC-7739 can return the ERC-1271 magic value, 0xffffffff (invalid) or 0x77390001 (detection).
54+
// If the returned value is 0xffffffff, fallback to ERC-7579 validation.
55+
bytes4 erc7739magic = ERC7739.isValidSignature(hash, signature);
56+
return erc7739magic == bytes4(0xffffffff) ? AccountERC7579.isValidSignature(hash, signature) : erc7739magic;
57+
}
58+
}`;
59+
60+
const url = remixURL(contractSource);
61+
const codeParam = url.searchParams.get('code');
62+
t.truthy(codeParam, 'Expected code search param to be set');
63+
64+
const decoded = decodeBase64(codeParam!);
65+
t.is(decoded, contractSource, 'Decoded code should equal original source');
66+
});

packages/ui/src/solidity/remix.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
export function remixURL(code: string, upgradeable = false, overrideRemixURL?: string): URL {
22
const remix = new URL(overrideRemixURL ?? 'https://remix.ethereum.org');
33

4-
const codeWithEscapedSpecialCharacters = Array.from(new TextEncoder().encode(code), b => String.fromCharCode(b)).join(
5-
'',
6-
);
4+
const encodedCode = btoa(String.fromCharCode(...new TextEncoder().encode(code))).replace(/=*$/, '');
75

8-
remix.searchParams.set('code', btoa(codeWithEscapedSpecialCharacters).replace(/=*$/, ''));
6+
remix.searchParams.set('code', encodedCode);
97

108
if (upgradeable) {
119
remix.searchParams.set('deployProxy', upgradeable.toString());

packages/ui/ts-node-register.cjs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/* eslint-disable */
2+
3+
// Configure ts-node to use a test-specific tsconfig
4+
const path = require('path');
5+
6+
require('ts-node').register({
7+
transpileOnly: true,
8+
project: path.join(__dirname, 'tsconfig.test.json'),
9+
});

packages/ui/tsconfig.test.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"extends": "./tsconfig.json",
3+
"compilerOptions": {
4+
"module": "NodeNext",
5+
"moduleResolution": "NodeNext",
6+
"noEmit": true,
7+
"lib": [
8+
"es2020",
9+
"dom",
10+
"dom.iterable"
11+
]
12+
},
13+
"include": [
14+
"src/**/*.ts"
15+
]
16+
}
17+

yarn.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1523,7 +1523,7 @@ autoprefixer@^10.4.2:
15231523
picocolors "^1.1.1"
15241524
postcss-value-parser "^4.2.0"
15251525

1526-
ava@^6.0.0:
1526+
ava@^6.0.0, ava@^6.1.2:
15271527
version "6.4.1"
15281528
resolved "https://registry.yarnpkg.com/ava/-/ava-6.4.1.tgz#89ce905d73bcd6c1d55bbba2598df3dc74e957dd"
15291529
integrity sha512-vxmPbi1gZx9zhAjHBgw81w/iEDKcrokeRk/fqDTyA2DQygZ0o+dUGRHFOtX8RA5N0heGJTTsIk7+xYxitDb61Q==
@@ -5936,7 +5936,7 @@ ts-interface-checker@^0.1.9:
59365936
resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699"
59375937
integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==
59385938

5939-
ts-node@^10.4.0:
5939+
ts-node@^10.4.0, ts-node@^10.9.2:
59405940
version "10.9.2"
59415941
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f"
59425942
integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==

0 commit comments

Comments
 (0)