Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,33 @@ pub enum ProxyType {
RootClaim,
}

impl TryFrom<u8> for ProxyType {
type Error = ();

fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Any),
1 => Ok(Self::Owner),
2 => Ok(Self::NonCritical),
3 => Ok(Self::NonTransfer),
4 => Ok(Self::Senate),
5 => Ok(Self::NonFungible),
6 => Ok(Self::Triumvirate),
7 => Ok(Self::Governance),
8 => Ok(Self::Staking),
9 => Ok(Self::Registration),
10 => Ok(Self::Transfer),
11 => Ok(Self::SmallTransfer),
12 => Ok(Self::RootWeights),
13 => Ok(Self::ChildKeys),
14 => Ok(Self::SudoUncheckedSetCode),
15 => Ok(Self::SwapHotkey),
16 => Ok(Self::SubnetLeaseBeneficiary),
_ => Err(()),
}
}
}

impl Default for ProxyType {
// allow all Calls; required to be most permissive
fn default() -> Self {
Expand Down
2 changes: 1 addition & 1 deletion evm-tests/src/address-utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Address } from "viem"
import { encodeAddress } from "@polkadot/util-crypto";
import { ss58Address } from "@polkadot-labs/hdkd-helpers";
import { ss58Address, ss58Decode } from "@polkadot-labs/hdkd-helpers";
import { hexToU8a } from "@polkadot/util";
import { blake2AsU8a, decodeAddress } from "@polkadot/util-crypto";
import { Binary } from "polkadot-api";
Expand Down
148 changes: 148 additions & 0 deletions evm-tests/src/contracts/proxy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
export const IPROXY_ADDRESS = "0x000000000000000000000000000000000000080b";

export const IProxyABI = [
{
"inputs": [
{
"internalType": "uint8",
"name": "proxy_type",
"type": "uint8"
},
{
"internalType": "uint32",
"name": "delay",
"type": "uint32"
},
{
"internalType": "uint16",
"name": "index",
"type": "uint16"
}
],
"name": "createPureProxy",
"outputs": [
{
"internalType": "bytes32",
"name": "proxy",
"type": "bytes32"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "spawner",
"type": "bytes32"
},
{
"internalType": "uint8",
"name": "proxy_type",
"type": "uint8"
},
{
"internalType": "uint16",
"name": "index",
"type": "uint16"
},
{
"internalType": "uint32",
"name": "height",
"type": "uint32"
},
{
"internalType": "uint32",
"name": "ext_index",
"type": "uint32"
}
],
"name": "killPureProxy",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "real",
"type": "bytes32"
},
{
"internalType": "uint8[]",
"name": "force_proxy_type", // optional
"type": "uint8[]"
},
{
"internalType": "uint8[]",
"name": "call",
"type": "uint8[]"
}
],
"name": "proxyCall",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "removeProxies",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}, {
"inputs": [],
"name": "pokeDeposit",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "delegate",
"type": "bytes32"
},
{
"internalType": "uint8",
"name": "proxy_type",
"type": "uint8"
},
{
"internalType": "uint32",
"name": "delay",
"type": "uint32"
}
],
"name": "removeProxy",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "delegate",
"type": "bytes32"
},
{
"internalType": "uint8",
"name": "proxy_type",
"type": "uint8"
},
{
"internalType": "uint32",
"name": "delay",
"type": "uint32"
}
],
"name": "addProxy",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
];
163 changes: 163 additions & 0 deletions evm-tests/test/pure-proxy.precompile.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import * as assert from "assert";

import { getAliceSigner, getDevnetApi, getRandomSubstrateKeypair } from "../src/substrate"
import { generateRandomEthersWallet } from "../src/utils";
import { devnet, MultiAddress } from "@polkadot-api/descriptors"
import { PolkadotSigner, TypedApi } from "polkadot-api";
import { convertH160ToPublicKey, convertH160ToSS58, convertPublicKeyToSs58 } from "../src/address-utils"
import { IProxyABI, IPROXY_ADDRESS } from "../src/contracts/proxy"
import { ethers } from 'ethers';
import { forceSetBalanceToEthAddress, forceSetBalanceToSs58Address } from "../src/subtensor";
import { KeyPair } from "@polkadot-labs/hdkd-helpers";

import { decodeAddress } from "@polkadot/util-crypto";

async function getTransferCallCode(api: TypedApi<typeof devnet>, receiver: KeyPair, transferAmount: number) {

const unsignedTx = api.tx.Balances.transfer_keep_alive({
dest: MultiAddress.Id(convertPublicKeyToSs58(receiver.publicKey)),
value: BigInt(1000000000),
});
const encodedCallDataBytes = await unsignedTx.getEncodedData();

// encoded call should be 0x050300d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d02286bee
// const transferCall = encodedCallDataBytes

const data = encodedCallDataBytes.asBytes()

return [...data]
}

async function getProxies(api: TypedApi<typeof devnet>, address: string) {
const entries = await api.query.Proxy.Proxies.getEntries()
const result = []
for (const entry of entries) {
const proxyAddress = entry.keyArgs[0]
const values = entry.value
const proxies = values[0]
for (const proxy of proxies) {
if (proxy.delegate === address) {
result.push(proxyAddress)
}
}
}
return result
}

describe("Test pure proxy precompile", () => {
const evmWallet = generateRandomEthersWallet();
const evmWallet2 = generateRandomEthersWallet();
const evmWallet3 = generateRandomEthersWallet();
const receiver = getRandomSubstrateKeypair();

let api: TypedApi<typeof devnet>

let alice: PolkadotSigner;

before(async () => {
api = await getDevnetApi()
alice = await getAliceSigner();

await forceSetBalanceToEthAddress(api, evmWallet.address)
await forceSetBalanceToEthAddress(api, evmWallet2.address)
await forceSetBalanceToEthAddress(api, evmWallet3.address)
})

it("Call createPureProxy, then use proxy to call transfer", async () => {
const proxies = await getProxies(api, convertH160ToSS58(evmWallet.address))
const contract = new ethers.Contract(IPROXY_ADDRESS, IProxyABI, evmWallet)
console.log("evmWallet", evmWallet.address)

const type = 0;
const delay = 0;
const index = 0;
const tx = await contract.createPureProxy(type, delay, index)
const response = await tx.wait()
console.log("response", response.blockNumber)

const proxiesAfterAdd = await getProxies(api, convertH160ToSS58(evmWallet.address))

const length = proxiesAfterAdd.length
assert.equal(length, proxies.length + 1, "proxy should be set")
const proxy = proxiesAfterAdd[proxiesAfterAdd.length - 1]

await forceSetBalanceToSs58Address(api, proxy)
const balance = (await api.query.System.Account.getValue(convertPublicKeyToSs58(receiver.publicKey))).data.free

const amount = 1000000000;

const callCode = await getTransferCallCode(api, receiver, amount)
const tx2 = await contract.proxyCall(decodeAddress(proxy), [type], callCode)
await tx2.wait()

const balanceAfter = (await api.query.System.Account.getValue(convertPublicKeyToSs58(receiver.publicKey))).data.free
assert.equal(balanceAfter, balance + BigInt(amount), "balance should be increased")
})

it("Call createPureProxy, add multiple proxies", async () => {
const contract = new ethers.Contract(IPROXY_ADDRESS, IProxyABI, evmWallet)
const type = 0;
const delay = 0;
const index = 0;
const proxies = await getProxies(api, convertH160ToSS58(evmWallet.address))
const length = proxies.length
for (let i = 0; i < 5; i++) {
const tx = await contract.createPureProxy(type, delay, index)
await tx.wait()

await new Promise(resolve => setTimeout(resolve, 500));
const currentProxies = await getProxies(api, convertH160ToSS58(evmWallet.address))
assert.equal(currentProxies.length, length + i + 1, "proxy should be set")
}
})

it("Call createPureProxy, edge cases, call via wrong proxy", async () => {
const contract = new ethers.Contract(IPROXY_ADDRESS, IProxyABI, evmWallet2)
const amount = 1000000000;
const callCode = await getTransferCallCode(api, receiver, amount)
const type = 0;

// call with wrong proxy
try {
const tx = await contract.proxyCall(receiver, [type], callCode)
await tx.wait()
} catch (error) {
assert.notEqual(error, undefined, "should fail if proxy not set")
}
})

it("Call createProxy, then use proxy to call transfer", async () => {
const proxies = await api.query.Proxy.Proxies.getValue(convertH160ToSS58(evmWallet2.address))
const contract = new ethers.Contract(IPROXY_ADDRESS, IProxyABI, evmWallet2)

const type = 0;
const delay = 0;

const tx = await contract.addProxy(convertH160ToPublicKey(evmWallet3.address), type, delay)
await tx.wait()


const proxiesAfterAdd = await await api.query.Proxy.Proxies.getValue(convertH160ToSS58(evmWallet2.address))

const length = proxiesAfterAdd[0].length
assert.equal(length, proxies[0].length + 1, "proxy should be set")
const proxy = proxiesAfterAdd[0][proxiesAfterAdd[0].length - 1]

assert.equal(proxy.delegate, convertH160ToSS58(evmWallet3.address), "proxy should be set")


const balance = (await api.query.System.Account.getValue(convertPublicKeyToSs58(receiver.publicKey))).data.free

const amount = 1000000000;

const contract2 = new ethers.Contract(IPROXY_ADDRESS, IProxyABI, evmWallet3)


const callCode = await getTransferCallCode(api, receiver, amount)
const tx2 = await contract2.proxyCall(convertH160ToPublicKey(evmWallet2.address), [type], callCode)
await tx2.wait()

const balanceAfter = (await api.query.System.Account.getValue(convertPublicKeyToSs58(receiver.publicKey))).data.free
assert.equal(balanceAfter, balance + BigInt(amount), "balance should be increased")
})
});
4 changes: 2 additions & 2 deletions pallets/admin-utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,8 @@ pub mod pallet {
Alpha,
/// Enum for crowdloan precompile
Crowdloan,
/// Pure proxy precompile
PureProxy,
/// Proxy precompile
Proxy,
/// Leasing precompile
Leasing,
}
Expand Down
Loading
Loading