Skip to content

Commit

Permalink
[SDK] Support more types of Minimal Proxy (#429)
Browse files Browse the repository at this point in the history
  • Loading branch information
jakeloo committed Dec 1, 2022
1 parent 1bfb91d commit 034a257
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 7 deletions.
5 changes: 5 additions & 0 deletions .changeset/tough-coats-beam.md
@@ -0,0 +1,5 @@
---
"@thirdweb-dev/sdk": patch
---

Extract more minimal proxy impl address
56 changes: 49 additions & 7 deletions packages/sdk/src/evm/common/feature-detection.ts
Expand Up @@ -266,6 +266,42 @@ function toJSType(
return jsType;
}

/**
* @internal
* @param bytecode
*/
export function extractMinimalProxyImplementationAddress(
bytecode: string,
): string | undefined {
// EIP-1167 clone minimal proxy - https://eips.ethereum.org/EIPS/eip-1167
if (bytecode.startsWith("0x363d3d373d3d3d363d73")) {
const implementationAddress = bytecode.slice(22, 62);
return `0x${implementationAddress}`;
}

// Minimal Proxy with receive() from 0xSplits - https://github.com/0xSplits/splits-contracts/blob/c7b741926ec9746182d0d1e2c4c2046102e5d337/contracts/libraries/Clones.sol
if (bytecode.startsWith("0x36603057343d5230")) {
// +40 = size of addr
const implementationAddress = bytecode.slice(122, 122 + 40);
return `0x${implementationAddress}`;
}

// 0age's minimal proxy - https://medium.com/coinmonks/the-more-minimal-proxy-5756ae08ee48
if (bytecode.startsWith("0x3d3d3d3d363d3d37363d73")) {
// +40 = size of addr
const implementationAddress = bytecode.slice(24, 24 + 40);
return `0x${implementationAddress}`;
}

// vyper's minimal proxy (uniswap v1) - https://etherscan.io/address/0x09cabec1ead1c0ba254b09efb3ee13841712be14#code
if (bytecode.startsWith("0x366000600037611000600036600073")) {
const implementationAddress = bytecode.slice(32, 32 + 40);
return `0x${implementationAddress}`;
}

return undefined;
}

/**
* @internal
* @param address
Expand All @@ -282,14 +318,20 @@ export async function resolveContractUriFromAddress(
`Contract at ${address} does not exist on chain '${chain.name}' (chainId: ${chain.chainId})`,
);
}
// EIP-1167 clone proxy - https://eips.ethereum.org/EIPS/eip-1167
if (bytecode.startsWith("0x363d3d373d3d3d363d")) {
const implementationAddress = bytecode.slice(22, 62);
return await resolveContractUriFromAddress(
`0x${implementationAddress}`,
provider,
);

try {
const implementationAddress =
extractMinimalProxyImplementationAddress(bytecode);
if (implementationAddress) {
return await resolveContractUriFromAddress(
`0x${implementationAddress}`,
provider,
);
}
} catch (e) {
// ignore
}

// EIP-1967 proxy storage slots - https://eips.ethereum.org/EIPS/eip-1967
try {
const proxyStorage = await provider.getStorageAt(
Expand Down
81 changes: 81 additions & 0 deletions packages/sdk/test/evm/feature-detection.test.ts
@@ -0,0 +1,81 @@
import {
extractMinimalProxyImplementationAddress,
ThirdwebSDK,
} from "../../src/evm";
import { expectError, signers, sdk } from "./before-setup";
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
import { assert, expect } from "chai";
import { ethers } from "ethers";
import invariant from "tiny-invariant";

global.fetch = require("cross-fetch");

describe("Custom Contracts", async () => {
let nftContractAddress: string;
let adminWallet: SignerWithAddress;
let realSDK: ThirdwebSDK;

before(async () => {
[adminWallet] = signers;
realSDK = new ThirdwebSDK(adminWallet);
});

beforeEach(async () => {
sdk.updateSignerOrProvider(adminWallet);
realSDK.updateSignerOrProvider(adminWallet);

nftContractAddress = await sdk.deployer.deployNFTCollection({
name: `NFT`,
description: "Test contract from tests",
image: "image",
primary_sale_recipient: adminWallet.address,
seller_fee_basis_points: 500,
fee_recipient: adminWallet.address,
platform_fee_basis_points: 10,
platform_fee_recipient: adminWallet.address,
});
});

it("should extract implementation address for eip-1167 minimal proxy", async () => {
// https://eips.ethereum.org/EIPS/eip-1167#specification
const code =
"0x363d3d373d3d3d363d73bebebebebebebebebebebebebebebebebebebebe5af43d82803e903d91602b57fd5bf3";
const implementationAddress =
extractMinimalProxyImplementationAddress(code);
expect(implementationAddress).to.equal(
"0xbebebebebebebebebebebebebebebebebebebebe",
);
});

it("should extract implementation address for minimal proxy with receive", async () => {
// https://github.com/0xSplits/splits-contracts/blob/c7b741926ec9746182d0d1e2c4c2046102e5d337/contracts/libraries/Clones.sol#L32
const code =
"0x36603057343d52307f830d2d700a97af574b186c80d40429385d24241565b08a7c559ba283a964d9b160203da23d3df35b3d3d3d3d363d3d37363d73bebebebebebebebebebebebebebebebebebebebe5af43d3d93803e605b57fd5bf3";
const implementationAddress =
extractMinimalProxyImplementationAddress(code);
expect(implementationAddress).to.equal(
"0xbebebebebebebebebebebebebebebebebebebebe",
);
});

it("should extract implementation address for more minimal proxy", async () => {
// https://medium.com/coinmonks/the-more-minimal-proxy-5756ae08ee48
const code =
"0x3d3d3d3d363d3d37363d73bebebebebebebebebebebebebebebebebebebebe5af43d3d93803e602a57fd5bf3";
const implementationAddress =
extractMinimalProxyImplementationAddress(code);
expect(implementationAddress).to.equal(
"0xbebebebebebebebebebebebebebebebebebebebe",
);
});

it("should extract implementation address for older vyper (uniswap v1) proxy", async () => {
const code =
"0x366000600037611000600036600073bebebebebebebebebebebebebebebebebebebebe5af41558576110006000f3";
const implementationAddress =
extractMinimalProxyImplementationAddress(code);
expect(implementationAddress).to.equal(
"0xbebebebebebebebebebebebebebebebebebebebe",
);
});
});

0 comments on commit 034a257

Please sign in to comment.