ethers/lib/utils#Interface TypeScript Examples

The following examples show how to use ethers/lib/utils#Interface. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: test-precompile-democracy.ts    From moonbeam with GNU General Public License v3.0 6 votes vote down vote up
deployAndInterfaceContract = async (
  context: DevTestContext,
  contractName: string
): Promise<Interface> => {
  // deploy contract
  const { rawTx } = await createContract(context, contractName);
  await context.createBlock({ transactions: [rawTx] });
  // Instantiate interface
  const contractData = await getCompiled(contractName);
  return new ethers.utils.Interface(contractData.contract.abi);
}
Example #2
Source File: test-precompile-democracy.ts    From moonbeam with GNU General Public License v3.0 6 votes vote down vote up
notePreimagePrecompile = async <
  Call extends SubmittableExtrinsic<ApiType>,
  ApiType extends ApiTypes
>(
  context: DevTestContext,
  iFace: Interface,
  proposal: Call
): Promise<`0x${string}`> => {
  const encodedProposal = proposal.method.toHex();

  const data = iFace.encodeFunctionData(
    // action
    "note_preimage",
    [encodedProposal]
  );

  const tx = await createTransaction(context, {
    from: GENESIS_ACCOUNT,
    privateKey: GENESIS_ACCOUNT_PRIVATE_KEY,
    value: "0x0",
    gas: "0x200000",
    gasPrice: GAS_PRICE,
    to: ADDRESS_DEMO_PRECOMPILE,
    data,
  });

  await context.createBlock({
    transactions: [tx],
  });
  // return encodedHash
  return blake2AsHex(encodedProposal);
}
Example #3
Source File: test-precompile-democracy.ts    From moonbeam with GNU General Public License v3.0 6 votes vote down vote up
describeDevMoonbeam("Democracy - genesis and preimage", (context) => {
  let genesisAccount: KeyringPair;
  let iFace: Interface;

  before("Setup genesis account for substrate", async () => {
    const keyring = new Keyring({ type: "ethereum" });
    genesisAccount = await keyring.addFromUri(GENESIS_ACCOUNT_PRIVATE_KEY, null, "ethereum");
    iFace = await deployAndInterfaceContract(context, "Democracy");
  });
  it("should check initial state - no referendum", async function () {
    // referendumCount
    const referendumCount = await context.polkadotApi.query.democracy.referendumCount();
    expect(referendumCount.toHuman()).to.equal("0");
  });
  it("should check initial state - 0x0 ParachainBondAccount", async function () {
    // referendumCount
    const parachainBondInfo = await context.polkadotApi.query.parachainStaking.parachainBondInfo();
    expect(parachainBondInfo.toHuman()["account"]).to.equal(ZERO_ADDRESS);
  });
  it("notePreimage", async function () {
    // notePreimage
    const encodedHash = await notePreimagePrecompile(
      context,
      iFace,
      context.polkadotApi.tx.parachainStaking.setParachainBondAccount(GENESIS_ACCOUNT)
    );

    const preimageStatus = (await context.polkadotApi.query.democracy.preimages(
      encodedHash
    )) as any;
    expect(
      preimageStatus.unwrap().isAvailable && preimageStatus.unwrap().asAvailable.provider.toString()
    ).to.equal(GENESIS_ACCOUNT);
    expect(
      preimageStatus.unwrap().isAvailable && preimageStatus.unwrap().asAvailable.deposit.toString()
    ).to.equal("2200000000000000");
  });
});
Example #4
Source File: test-precompile-democracy.ts    From moonbeam with GNU General Public License v3.0 5 votes vote down vote up
describeDevMoonbeam("Democracy - propose", (context) => {
  let genesisAccount: KeyringPair;
  let encodedHash: `0x${string}`;
  let iFace: Interface;

  before("Setup genesis account for substrate", async () => {
    const keyring = new Keyring({ type: "ethereum" });
    genesisAccount = await keyring.addFromUri(GENESIS_ACCOUNT_PRIVATE_KEY, null, "ethereum");
    iFace = await deployAndInterfaceContract(context, "Democracy");

    // encodedHash
    encodedHash = await notePreimagePrecompile(
      context,
      iFace,
      context.polkadotApi.tx.parachainStaking.setParachainBondAccount(GENESIS_ACCOUNT)
    );
  });
  it("propose", async function () {
    // propose
    await sendPrecompileTx(
      context,
      ADDRESS_DEMO_PRECOMPILE,
      SELECTORS,
      GENESIS_ACCOUNT,
      GENESIS_ACCOUNT_PRIVATE_KEY,
      "propose",
      [encodedHash, numberToHex(Number(PROPOSAL_AMOUNT))]
    );

    // referendumCount
    const referendumCount = await context.polkadotApi.query.democracy.referendumCount();
    expect(referendumCount.toHuman()).to.equal("0");

    // publicPropCount
    const publicPropCount = await context.polkadotApi.query.democracy.publicPropCount();
    expect(publicPropCount.toHuman()).to.equal("1");

    // publicProps
    const publicProps = await context.polkadotApi.query.democracy.publicProps();
    // encodedHash
    expect((publicProps.toHuman() as any)[0][1]).to.equal(encodedHash);
    // prop author
    expect((publicProps.toHuman() as any)[0][2]).to.equal(GENESIS_ACCOUNT);
    // depositOf
    const depositOf = await context.polkadotApi.query.democracy.depositOf(0);
    expect((depositOf.toHuman() as any)[1]).to.equal("1,000,000,000,000,000,000,000");
  });
});
Example #5
Source File: expectEvent.ts    From balancer-v2-monorepo with GNU General Public License v3.0 5 votes vote down vote up
export function inIndirectReceipt(
  receipt: ContractReceipt,
  emitter: Interface,
  eventName: string,
  eventArgs = {},
  address?: string
): any {
  const decodedEvents = receipt.logs
    .filter((log) => (address ? log.address.toLowerCase() === address.toLowerCase() : true))
    .map((log) => {
      try {
        return emitter.parseLog(log);
      } catch {
        return undefined;
      }
    })
    .filter((e): e is LogDescription => e !== undefined);

  const expectedEvents = decodedEvents.filter((event) => event.name === eventName);
  expect(expectedEvents.length > 0).to.equal(true, `No '${eventName}' events found`);

  const exceptions: Array<string> = [];
  const event = expectedEvents.find(function (e) {
    for (const [k, v] of Object.entries(eventArgs)) {
      try {
        if (e.args == undefined) {
          throw new Error('Event has no arguments');
        }

        contains(e.args, k, v);
      } catch (error) {
        exceptions.push(error);
        return false;
      }
    }
    return true;
  });

  if (event === undefined) {
    // Each event entry may have failed to match for different reasons,
    // throw the first one
    throw exceptions[0];
  }

  return event;
}
Example #6
Source File: actions.ts    From balancer-v2-monorepo with GNU General Public License v3.0 5 votes vote down vote up
actionId = (instance: Contract, method: string, contractInterface?: Interface): Promise<string> => {
  const selector = (contractInterface ?? instance.interface).getSighash(method);
  return instance.getActionId(selector);
}
Example #7
Source File: chainedReferences.ts    From balancer-v2-monorepo with GNU General Public License v3.0 5 votes vote down vote up
mockBatchRelayerLibraryInterface = new Interface([
  'function setChainedReferenceValue(uint256 ref, uint256 value) public returns (uint256)',
  'function getChainedReferenceValue(uint256 ref) public',
  'event ChainedReferenceValueRead(uint256 value)',
])
Example #8
Source File: calls.test.ts    From useDApp with MIT License 5 votes vote down vote up
describe('decodeCallResult', () => {
  const erc20Abi = ['function name() view returns (string)']
  const call: Call = {
    contract: new Contract(`0x${'0'.repeat(39)}1`, new Interface(erc20Abi)),
    method: 'name',
    args: [],
  }

  it('one of arguments undefined', () => {
    const result: RawCallResult = { value: '', success: true }
    expect(decodeCallResult(undefined, result)).to.be.undefined
    expect(decodeCallResult(call, undefined)).to.be.undefined
  })

  it('call error', () => {
    const errorMessage = 'Testing error message'
    const errorResult: RawCallResult = {
      success: false,
      value: new utils.Interface(['function Error(string)']).encodeFunctionData('Error', [errorMessage]),
    }
    const { value, error } = decodeCallResult(call, errorResult) || {}
    expect(value).to.be.undefined
    expect(error?.message).to.equal(errorMessage)
  })

  it('decoding error', () => {
    const result: RawCallResult = {
      success: true,
      value: '0x0',
    }
    const { value, error } = decodeCallResult(call, result) || {}
    expect(value).to.be.undefined
    expect(error?.message.startsWith('hex data is odd-length')).to.be.true
  })

  it('success', () => {
    const name = 'Testing ERC20'
    const successResult: RawCallResult = {
      success: true,
      value: call.contract.interface.encodeFunctionResult('name', [name]),
    }
    expect(decodeCallResult(call, successResult)).to.deep.equal({ value: [name], error: undefined })
  })
})
Example #9
Source File: index.ts    From nova with GNU Affero General Public License v3.0 5 votes vote down vote up
/** Returns an array of function fragments that are stateful from an interface. */
export function getAllStatefulFragments(contractInterface: Interface) {
  return Object.values(contractInterface.functions).filter((f) => !f.constant);
}
Example #10
Source File: test-precompile.ts    From bodhi.js with Apache License 2.0 5 votes vote down vote up
ifaceOracle = new Interface(oracleAbi.abi)
Example #11
Source File: test-precompile.ts    From bodhi.js with Apache License 2.0 5 votes vote down vote up
ifaceEVM = new Interface(evmAbi.abi)
Example #12
Source File: test-precompile.ts    From bodhi.js with Apache License 2.0 5 votes vote down vote up
iface = new Interface(tokenAbi.abi)
Example #13
Source File: endpoint.test.ts    From bodhi.js with Apache License 2.0 5 votes vote down vote up
describe('eth_call', () => {
  const eth_call = rpcGet('eth_call');
  const eth_blockNumber = rpcGet('eth_blockNumber');

  type Call = (address: string) => Promise<string | bigint>;
  const _call =
    (method: string): Call =>
    async (address) => {
      const iface = new Interface(TokenABI.abi);

      const data = iface.encodeFunctionData(method);
      const blockNumber = (await eth_blockNumber()).data.result;
      const rawRes = (await eth_call([{ to: address, data }, blockNumber])).data.result;
      const [res] = iface.decodeFunctionResult(method, rawRes);

      return res;
    };

  const getName = _call('name');
  const getSymbol = _call('symbol');
  const getDecimals = _call('decimals');

  it('get correct procompile token info', async () => {
    // https://github.com/AcalaNetwork/Acala/blob/a5d9e61c74/node/service/src/chain_spec/mandala.rs#L628-L636
    // Native tokens need to registry in asset_registry module.
    const tokenMetaData = [
      {
        address: '0x0000000000000000000100000000000000000000',
        name: 'Acala',
        symbol: 'ACA',
        decimals: 12
      },
      {
        address: '0x0000000000000000000100000000000000000001',
        name: 'Acala Dollar',
        symbol: 'AUSD',
        decimals: 12
      },
      {
        address: '0x0000000000000000000100000000000000000002',
        name: 'Polkadot',
        symbol: 'DOT',
        decimals: 10
      },
      {
        address: '0x0000000000000000000100000000000000000003',
        name: 'Liquid DOT',
        symbol: 'LDOT',
        decimals: 10
      },
      {
        address: '0x0000000000000000000100000000000000000014',
        name: 'Ren Protocol BTC',
        symbol: 'RENBTC',
        decimals: 8
      }
    ];

    const tests = tokenMetaData.map(async ({ address, name, symbol, decimals }) => {
      const _name = await getName(address);
      const _symbol = await getSymbol(address);
      const _decimals = await getDecimals(address);

      expect(_name).to.equal(name);
      expect(_symbol).to.equal(symbol);
      expect(_decimals).to.equal(decimals);
    });

    await Promise.all(tests);
  });

  it.skip('get correct custom token info', async () => {
    // TODO: deploy custom erc20 and get correct info
  });
});
Example #14
Source File: check-chainlink.ts    From perpetual-protocol with GNU General Public License v3.0 5 votes vote down vote up
export async function checkChainlink(address: string, env: HardhatRuntimeEnvironment): Promise<void> {
    const AGGREGATOR_ABI = [
        "function decimals() view returns (uint8)",
        "function description() view returns (string memory)",
        "function latestAnswer() external view returns (int256)",
    ]

    const aggregator = await env.ethers.getContractAt(AGGREGATOR_ABI, address)

    const chainlinkL1Artifact = await artifacts.readArtifact(ContractFullyQualifiedName.ChainlinkL1)
    const chainlinkInterface = new Interface(chainlinkL1Artifact.abi)

    const l2PriceFeedArtifact = await artifacts.readArtifact(ContractFullyQualifiedName.L2PriceFeed)
    const l2PriceFeedInterface = new Interface(l2PriceFeedArtifact.abi)

    const [decimals, pair, latestPrice] = await Promise.all([
        aggregator.decimals(),
        aggregator.description(),
        aggregator.latestAnswer(),
    ])
    const [baseSymbol, quoteSymbol] = pair.split("/").map((symbol: string) => symbol.trim())
    const priceFeedKey = formatBytes32String(baseSymbol)
    const functionDataL1 = chainlinkInterface.encodeFunctionData("addAggregator", [priceFeedKey, address])
    const functionDataL2 = l2PriceFeedInterface.encodeFunctionData("addAggregator", [priceFeedKey])
    const metadataSet = await getContractMetadataSet(env.network.name)
    const filename = `addAggregator_${env.network.name}.txt`
    const latestPriceNum = Number.parseFloat(formatUnits(latestPrice, decimals))
    const lines = [
        `pair: ${pair}`,
        `base symbol: ${baseSymbol}`,
        `quote symbol: ${quoteSymbol}`,
        `latest price: ${latestPriceNum}`,
        `maxHoldingBaseAsset (personal): ${100_000 / latestPriceNum}`,
        `openInterestNotionalCap (total): 2_000_000`,
        ``,
        `price feed key: ${priceFeedKey}`,
        `aggregator address: ${address}`,
        `functionData(ChainlinkL1,ChainlinkPriceFeed): ${functionDataL1}`,
        `functionData(L2PriceFeed): ${functionDataL2}`,
        "",
        "Copy lines below to setup environment variables:",
        `export ${env.network.name.toUpperCase()}_TOKEN_SYMBOL=${baseSymbol}`,
        `export ${env.network.name.toUpperCase()}_PRICE_FEED_KEY=${priceFeedKey}`,
        "",
        `ABI information for gnosis safe is saved to ${filename}`,
    ]

    const aggregatorInfoLines = [
        `ChainlinkL1 abi: ${metadataSet.ChainlinkL1.abi}`,
        `ChainlinkL1 proxy address: ${metadataSet.ChainlinkL1.proxy}`,
        "",
        `L2PriceFeed abi: ${metadataSet.L2PriceFeed.abi}`,
        `L2PriceFeed proxy address: ${metadataSet.L2PriceFeed.proxy}`,
        "",
        `InsuranceFund abi: ${metadataSet.InsuranceFund.abi}`,
        `InsuranceFund proxy address: ${metadataSet.InsuranceFund.proxy}`,
    ]
    await fs.promises.writeFile(filename, aggregatorInfoLines.join("\n"))

    console.log(lines.join("\n"))
}
Example #15
Source File: test-precompile-democracy.ts    From moonbeam with GNU General Public License v3.0 4 votes vote down vote up
describeDevMoonbeam("Democracy - vote on referendum", (context) => {
  let genesisAccount: KeyringPair, alith: KeyringPair;
  let encodedHash: `0x${string}`;
  let enactmentPeriod, votingPeriod;
  let iFace: Interface;

  before("Setup genesis account for substrate", async () => {
    const keyring = new Keyring({ type: "ethereum" });
    genesisAccount = await keyring.addFromUri(GENESIS_ACCOUNT_PRIVATE_KEY, null, "ethereum");
    alith = await keyring.addFromUri(ALITH_PRIV_KEY, null, "ethereum");

    iFace = await deployAndInterfaceContract(context, "Democracy");

    // enactmentPeriod
    enactmentPeriod = await context.polkadotApi.consts.democracy.enactmentPeriod;
    // votingPeriod
    votingPeriod = await context.polkadotApi.consts.democracy.votingPeriod;

    // encodedHash
    encodedHash = await notePreimagePrecompile(
      context,
      iFace,
      context.polkadotApi.tx.parachainStaking.setParachainBondAccount(GENESIS_ACCOUNT)
    );

    // propose
    await sendPrecompileTx(
      context,
      ADDRESS_DEMO_PRECOMPILE,
      SELECTORS,
      GENESIS_ACCOUNT,
      GENESIS_ACCOUNT_PRIVATE_KEY,
      "propose",
      [encodedHash, numberToHex(Number(PROPOSAL_AMOUNT))]
    );

    // second
    await sendPrecompileTx(
      context,
      ADDRESS_DEMO_PRECOMPILE,
      SELECTORS,
      ALITH,
      ALITH_PRIV_KEY,
      "second",
      [numberToHex(0), numberToHex(1000)]
    );
  });
  it("check enactment period", async function () {
    // enactmentPeriod
    expect(enactmentPeriod.toHuman()).to.equal("7,200");
  });
  it("check voting Period", async function () {
    // votingPeriod
    expect(votingPeriod.toHuman()).to.equal("36,000");
  });
  it("vote", async function () {
    this.timeout(2000000);
    // let Launchperiod elapse to turn the proposal into a referendum
    // launchPeriod minus the 3 blocks that already elapsed
    for (let i = 0; i < 7200 - 3; i++) {
      await context.createBlock();
    }
    // vote
    await sendPrecompileTx(
      context,
      ADDRESS_DEMO_PRECOMPILE,
      SELECTORS,
      ALITH,
      ALITH_PRIV_KEY,
      "standard_vote",
      [numberToHex(0), "0x01", numberToHex(Number(VOTE_AMOUNT)), numberToHex(1)]
    );

    // referendumInfoOf
    const referendumInfoOf = await context.polkadotApi.query.democracy.referendumInfoOf(0);
    console.log("referendumInfoOf.toHuman() ", referendumInfoOf.toHuman());
    expect((referendumInfoOf.toHuman() as any).Ongoing.proposalHash).to.equal(encodedHash);
    expect((referendumInfoOf.toHuman() as any).Ongoing.tally.ayes).to.equal(
      "10,000,000,000,000,000,000"
    );
    expect((referendumInfoOf.toHuman() as any).Ongoing.tally.turnout).to.equal(
      "10,000,000,000,000,000,000"
    );

    // let votePeriod + enactmentPeriod elapse to turn the proposal into a referendum
    for (let i = 0; i < Number(votingPeriod) + Number(enactmentPeriod) + 10; i++) {
      await context.createBlock();
    }
    let parachainBondInfo = await context.polkadotApi.query.parachainStaking.parachainBondInfo();
    expect(parachainBondInfo.toHuman()["account"]).to.equal(GENESIS_ACCOUNT);
  });
});
Example #16
Source File: index.tsx    From nouns-monorepo with GNU General Public License v3.0 4 votes vote down vote up
ProposalTransactionFormModal = ({
  show,
  onHide,
  onProposalTransactionAdded,
}: ProposalTransactionFormModalProps) => {
  const [address, setAddress] = useState('');
  const [abi, setABI] = useState<Interface>();
  const [value, setValue] = useState('');
  const [func, setFunction] = useState('');
  const [args, setArguments] = useState<string[]>([]);

  const [isABIUploadValid, setABIUploadValid] = useState<boolean>();
  const [abiFileName, setABIFileName] = useState<string | undefined>('');

  const addressValidator = (s: string) => {
    if (!utils.isAddress(s)) {
      return false;
    }
    // To avoid blocking stepper progress, do not `await`
    populateABIIfExists(s);
    return true;
  };

  const valueValidator = (v: string) => !v || !new BigNumber(v).isNaN();

  const argumentsValidator = (a: string[]) => {
    if (!func) {
      return true;
    }

    try {
      return !!abi?._encodeParams(abi?.functions[func]?.inputs, args);
    } catch {
      return false;
    }
  };

  const setArgument = (index: number, value: string) => {
    const values = [...args];
    values[index] = value;
    setArguments(values);
  };

  let abiErrorTimeout: NodeJS.Timeout;
  const setABIInvalid = () => {
    setABIUploadValid(false);
    setABIFileName(undefined);
    abiErrorTimeout = setTimeout(() => {
      setABIUploadValid(undefined);
    }, 5_000);
  };

  const validateAndSetABI = (file: File | undefined) => {
    if (abiErrorTimeout) {
      clearTimeout(abiErrorTimeout);
    }
    if (!file) {
      return;
    }

    const reader = new FileReader();
    reader.onload = async e => {
      try {
        const abi = e?.target?.result?.toString() ?? '';
        setABI(new Interface(JSON.parse(abi)));
        setABIUploadValid(true);
        setABIFileName(file.name);
      } catch {
        setABIInvalid();
      }
    };
    reader.readAsText(file);
  };

  const getContractInformation = async (address: string) => {
    const response = await fetch(buildEtherscanApiQuery(address));
    const json = await response.json();
    return json?.result?.[0];
  };

  const getABI = async (address: string) => {
    let info = await getContractInformation(address);
    if (info?.Proxy === '1' && utils.isAddress(info?.Implementation)) {
      info = await getContractInformation(info.Implementation);
    }
    return info.ABI;
  };

  const populateABIIfExists = async (address: string) => {
    if (abiErrorTimeout) {
      clearTimeout(abiErrorTimeout);
    }

    try {
      const result = await getABI(address);
      setABI(new Interface(JSON.parse(result)));
      setABIUploadValid(true);
      setABIFileName('etherscan-abi-download.json');
    } catch {
      setABIUploadValid(undefined);
      setABIFileName(undefined);
    }
  };

  const stepForwardOrCallback = () => {
    if (currentStep !== steps.length - 1) {
      return stepForward();
    }
    onProposalTransactionAdded({
      address,
      value: value ? utils.parseEther(value).toString() : '0',
      signature: func,
      calldata: (func && abi?._encodeParams(abi?.functions[func]?.inputs, args)) || '0x',
    });
    clearState();
  };

  const steps = [
    {
      label: 'Address',
      name: 'address',
      validator: () => addressValidator(address),
    },
    {
      label: 'Value',
      name: 'value',
      validator: () => valueValidator(value),
    },
    {
      label: 'Function',
      name: 'function',
    },
    {
      label: 'Arguments',
      name: 'arguments',
      validator: () => argumentsValidator(args),
    },
    {
      label: 'Summary',
      name: 'summary',
    },
  ];

  const { stepForward, stepBackwards, currentStep } = useStepProgress({
    steps,
    startingStep: 0,
  });

  const clearState = () => {
    setAddress('');
    setABI(undefined);
    setValue('');
    setFunction('');
    setArguments([]);
    setABIUploadValid(undefined);
    setABIFileName(undefined);

    for (let i = currentStep; i > 0; i--) {
      stepBackwards();
    }
  };

  return (
    <Modal
      show={show}
      onHide={() => {
        onHide();
        clearState();
      }}
      dialogClassName={classes.transactionFormModal}
      centered
    >
      <Modal.Header closeButton>
        <Modal.Title>
          <Trans>Add a Proposal Transaction</Trans>
        </Modal.Title>
      </Modal.Header>
      <Modal.Body>
        <StepProgressBar className={classes.stepProgressBar} steps={steps} />
        <Step step={0}>
          <label htmlFor="callee-address">
            <Trans>Address (Callee or Recipient)</Trans>
          </label>
          <FormControl
            value={address}
            type="text"
            id="callee-address"
            onChange={e => setAddress(e.target.value)}
          />
        </Step>
        <Step step={1}>
          <label htmlFor="eth-value">
            <Trans>Value in ETH (Optional)</Trans>
          </label>
          <FormControl value={value} id="eth-value" onChange={e => setValue(e.target.value)} />
        </Step>
        <Step step={2}>
          <label htmlFor="function">
            <Trans>Function (Optional)</Trans>
          </label>
          <FormControl
            value={func}
            as="select"
            id="function"
            onChange={e => setFunction(e.target.value)}
          >
            <option className="text-muted">Select Contract Function</option>
            {abi && Object.keys(abi.functions).map(func => <option value={func}>{func}</option>)}
          </FormControl>
          <label style={{ marginTop: '1rem' }} htmlFor="import-abi">
            {abiFileName === 'etherscan-abi-download.json' ? abiFileName : 'ABI'}
          </label>
          <Form.Control
            type="file"
            id="import-abi"
            accept="application/JSON"
            isValid={isABIUploadValid}
            isInvalid={isABIUploadValid === false}
            onChange={(e: ChangeEvent<HTMLInputElement>) => validateAndSetABI(e.target.files?.[0])}
          />
        </Step>
        <Step step={3}>
          {abi?.functions[func]?.inputs?.length ? (
            <FormGroup as={Row}>
              {abi?.functions[func]?.inputs.map((input, i) => (
                <>
                  <FormLabel column sm="3">
                    {input.name}
                  </FormLabel>
                  <Col sm="9">
                    <InputGroup className="mb-2">
                      <InputGroup.Text className={classes.inputGroupText}>
                        {input.type}
                      </InputGroup.Text>
                      <FormControl
                        value={args[i] ?? ''}
                        onChange={e => setArgument(i, e.target.value)}
                      />
                    </InputGroup>
                  </Col>
                </>
              ))}
            </FormGroup>
          ) : (
            <Trans>No arguments required </Trans>
          )}
        </Step>
        <Step step={4}>
          <Row>
            <Col sm="3">
              <b>
                <Trans>Address</Trans>
              </b>
            </Col>
            <Col sm="9" className="text-break">
              <a href={buildEtherscanAddressLink(address)} target="_blank" rel="noreferrer">
                {address}
              </a>
            </Col>
          </Row>
          <Row>
            <Col sm="3">
              <b>
                <Trans>Value</Trans>
              </b>
            </Col>
            <Col sm="9">{value ? `${value} ETH` : <Trans>None</Trans>}</Col>
          </Row>
          <Row>
            <Col sm="3">
              <b>
                <Trans>Function</Trans>
              </b>
            </Col>
            <Col sm="9" className="text-break">
              {func || <Trans>None</Trans>}
            </Col>
          </Row>
          <Row>
            <Col sm="3">
              <b>
                <Trans>Arguments</Trans>
              </b>
            </Col>
            <Col sm="9">
              <hr />
            </Col>
            <Col sm="9">{abi?.functions[func]?.inputs?.length ? '' : <Trans>None</Trans>}</Col>
          </Row>
          {abi?.functions[func]?.inputs.map((input, i) => (
            <Row key={i}>
              <Col sm="3" className={classes.functionName}>
                {i + 1}. {input.name}
              </Col>
              <Col sm="9" className="text-break">
                {args[i]}
              </Col>
            </Row>
          ))}
        </Step>
        <div className="d-flex justify-content-between align-items-center pt-3">
          <Button
            onClick={stepBackwards}
            variant="outline-secondary"
            size="lg"
            disabled={currentStep === 0}
          >
            <Trans>Back</Trans>
          </Button>
          <Button onClick={stepForwardOrCallback} variant="primary" size="lg">
            {currentStep !== steps.length - 1 ? (
              <Trans>Next</Trans>
            ) : (
              <Trans>Add Transaction</Trans>
            )}
          </Button>
        </div>
      </Modal.Body>
    </Modal>
  );
}
Example #17
Source File: deploy.ts    From nouns-monorepo with GNU General Public License v3.0 4 votes vote down vote up
task('deploy', 'Deploys NFTDescriptor, NounsDescriptor, NounsSeeder, and NounsToken')
  .addFlag('autoDeploy', 'Deploy all contracts without user interaction')
  .addOptionalParam('weth', 'The WETH contract address', undefined, types.string)
  .addOptionalParam('noundersdao', 'The nounders DAO contract address', undefined, types.string)
  .addOptionalParam(
    'auctionTimeBuffer',
    'The auction time buffer (seconds)',
    5 * 60 /* 5 minutes */,
    types.int,
  )
  .addOptionalParam(
    'auctionReservePrice',
    'The auction reserve price (wei)',
    1 /* 1 wei */,
    types.int,
  )
  .addOptionalParam(
    'auctionMinIncrementBidPercentage',
    'The auction min increment bid percentage (out of 100)',
    2 /* 2% */,
    types.int,
  )
  .addOptionalParam(
    'auctionDuration',
    'The auction duration (seconds)',
    60 * 60 * 24 /* 24 hours */,
    types.int,
  )
  .addOptionalParam(
    'timelockDelay',
    'The timelock delay (seconds)',
    60 * 60 * 24 * 2 /* 2 days */,
    types.int,
  )
  .addOptionalParam(
    'votingPeriod',
    'The voting period (blocks)',
    Math.round(4 * 60 * 24 * (60 / 13)) /* 4 days (13s blocks) */,
    types.int,
  )
  .addOptionalParam(
    'votingDelay',
    'The voting delay (blocks)',
    Math.round(3 * 60 * 24 * (60 / 13)) /* 3 days (13s blocks) */,
    types.int,
  )
  .addOptionalParam(
    'proposalThresholdBps',
    'The proposal threshold (basis points)',
    100 /* 1% */,
    types.int,
  )
  .addOptionalParam(
    'quorumVotesBps',
    'Votes required for quorum (basis points)',
    1_000 /* 10% */,
    types.int,
  )
  .setAction(async (args, { ethers }) => {
    const network = await ethers.provider.getNetwork();
    const [deployer] = await ethers.getSigners();

    // prettier-ignore
    const proxyRegistryAddress = proxyRegistries[network.chainId] ?? proxyRegistries[ChainId.Rinkeby];

    if (!args.noundersdao) {
      console.log(
        `Nounders DAO address not provided. Setting to deployer (${deployer.address})...`,
      );
      args.noundersdao = deployer.address;
    }
    if (!args.weth) {
      const deployedWETHContract = wethContracts[network.chainId];
      if (!deployedWETHContract) {
        throw new Error(
          `Can not auto-detect WETH contract on chain ${network.name}. Provide it with the --weth arg.`,
        );
      }
      args.weth = deployedWETHContract;
    }

    const nonce = await deployer.getTransactionCount();
    const expectedAuctionHouseProxyAddress = ethers.utils.getContractAddress({
      from: deployer.address,
      nonce: nonce + AUCTION_HOUSE_PROXY_NONCE_OFFSET,
    });
    const expectedNounsDAOProxyAddress = ethers.utils.getContractAddress({
      from: deployer.address,
      nonce: nonce + GOVERNOR_N_DELEGATOR_NONCE_OFFSET,
    });
    const deployment: Record<ContractName, DeployedContract> = {} as Record<
      ContractName,
      DeployedContract
    >;
    const contracts: Record<ContractName, ContractDeployment> = {
      NFTDescriptor: {},
      NounsDescriptor: {
        libraries: () => ({
          NFTDescriptor: deployment.NFTDescriptor.address,
        }),
      },
      NounsSeeder: {},
      NounsToken: {
        args: [
          args.noundersdao,
          expectedAuctionHouseProxyAddress,
          () => deployment.NounsDescriptor.address,
          () => deployment.NounsSeeder.address,
          proxyRegistryAddress,
        ],
      },
      NounsAuctionHouse: {
        waitForConfirmation: true,
      },
      NounsAuctionHouseProxyAdmin: {},
      NounsAuctionHouseProxy: {
        args: [
          () => deployment.NounsAuctionHouse.address,
          () => deployment.NounsAuctionHouseProxyAdmin.address,
          () =>
            new Interface(NounsAuctionHouseABI).encodeFunctionData('initialize', [
              deployment.NounsToken.address,
              args.weth,
              args.auctionTimeBuffer,
              args.auctionReservePrice,
              args.auctionMinIncrementBidPercentage,
              args.auctionDuration,
            ]),
        ],
        waitForConfirmation: true,
        validateDeployment: () => {
          const expected = expectedAuctionHouseProxyAddress.toLowerCase();
          const actual = deployment.NounsAuctionHouseProxy.address.toLowerCase();
          if (expected !== actual) {
            throw new Error(
              `Unexpected auction house proxy address. Expected: ${expected}. Actual: ${actual}.`,
            );
          }
        },
      },
      NounsDAOExecutor: {
        args: [expectedNounsDAOProxyAddress, args.timelockDelay],
      },
      NounsDAOLogicV1: {
        waitForConfirmation: true,
      },
      NounsDAOProxy: {
        args: [
          () => deployment.NounsDAOExecutor.address,
          () => deployment.NounsToken.address,
          args.noundersdao,
          () => deployment.NounsDAOExecutor.address,
          () => deployment.NounsDAOLogicV1.address,
          args.votingPeriod,
          args.votingDelay,
          args.proposalThresholdBps,
          args.quorumVotesBps,
        ],
        waitForConfirmation: true,
        validateDeployment: () => {
          const expected = expectedNounsDAOProxyAddress.toLowerCase();
          const actual = deployment.NounsDAOProxy.address.toLowerCase();
          if (expected !== actual) {
            throw new Error(
              `Unexpected Nouns DAO proxy address. Expected: ${expected}. Actual: ${actual}.`,
            );
          }
        },
      },
    };

    for (const [name, contract] of Object.entries(contracts)) {
      let gasPrice = await ethers.provider.getGasPrice();
      if (!args.autoDeploy) {
        const gasInGwei = Math.round(Number(ethers.utils.formatUnits(gasPrice, 'gwei')));

        promptjs.start();

        const result = await promptjs.get([
          {
            properties: {
              gasPrice: {
                type: 'integer',
                required: true,
                description: 'Enter a gas price (gwei)',
                default: gasInGwei,
              },
            },
          },
        ]);
        gasPrice = ethers.utils.parseUnits(result.gasPrice.toString(), 'gwei');
      }

      const factory = await ethers.getContractFactory(name, {
        libraries: contract?.libraries?.(),
      });

      const deploymentGas = await factory.signer.estimateGas(
        factory.getDeployTransaction(
          ...(contract.args?.map(a => (typeof a === 'function' ? a() : a)) ?? []),
          {
            gasPrice,
          },
        ),
      );
      const deploymentCost = deploymentGas.mul(gasPrice);

      console.log(
        `Estimated cost to deploy ${name}: ${ethers.utils.formatUnits(
          deploymentCost,
          'ether',
        )} ETH`,
      );

      if (!args.autoDeploy) {
        const result = await promptjs.get([
          {
            properties: {
              confirm: {
                pattern: /^(DEPLOY|SKIP|EXIT)$/,
                description:
                  'Type "DEPLOY" to confirm, "SKIP" to skip this contract, or "EXIT" to exit.',
              },
            },
          },
        ]);
        if (result.operation === 'SKIP') {
          console.log(`Skipping ${name} deployment...`);
          continue;
        }
        if (result.operation === 'EXIT') {
          console.log('Exiting...');
          return;
        }
      }
      console.log(`Deploying ${name}...`);

      const deployedContract = await factory.deploy(
        ...(contract.args?.map(a => (typeof a === 'function' ? a() : a)) ?? []),
        {
          gasPrice,
        },
      );

      if (contract.waitForConfirmation) {
        await deployedContract.deployed();
      }

      deployment[name as ContractName] = {
        name,
        instance: deployedContract,
        address: deployedContract.address,
        constructorArguments: contract.args?.map(a => (typeof a === 'function' ? a() : a)) ?? [],
        libraries: contract?.libraries?.() ?? {},
      };

      contract.validateDeployment?.();

      console.log(`${name} contract deployed to ${deployedContract.address}`);
    }

    return deployment;
  });
Example #18
Source File: deploy-local.ts    From nouns-monorepo with GNU General Public License v3.0 4 votes vote down vote up
task('deploy-local', 'Deploy contracts to hardhat')
  .addOptionalParam('noundersdao', 'The nounders DAO contract address')
  .addOptionalParam('auctionTimeBuffer', 'The auction time buffer (seconds)', 30, types.int) // Default: 30 seconds
  .addOptionalParam('auctionReservePrice', 'The auction reserve price (wei)', 1, types.int) // Default: 1 wei
  .addOptionalParam(
    'auctionMinIncrementBidPercentage',
    'The auction min increment bid percentage (out of 100)', // Default: 5%
    5,
    types.int,
  )
  .addOptionalParam('auctionDuration', 'The auction duration (seconds)', 60 * 2, types.int) // Default: 2 minutes
  .addOptionalParam('timelockDelay', 'The timelock delay (seconds)', 60 * 60 * 24 * 2, types.int) // Default: 2 days
  .addOptionalParam('votingPeriod', 'The voting period (blocks)', 4 * 60 * 24 * 3, types.int) // Default: 3 days
  .addOptionalParam('votingDelay', 'The voting delay (blocks)', 1, types.int) // Default: 1 block
  .addOptionalParam('proposalThresholdBps', 'The proposal threshold (basis points)', 500, types.int) // Default: 5%
  .addOptionalParam('quorumVotesBps', 'Votes required for quorum (basis points)', 1_000, types.int) // Default: 10%
  .setAction(async (args, { ethers }) => {
    const network = await ethers.provider.getNetwork();
    if (network.chainId !== 31337) {
      console.log(`Invalid chain id. Expected 31337. Got: ${network.chainId}.`);
      return;
    }

    const proxyRegistryAddress = '0xa5409ec958c83c3f309868babaca7c86dcb077c1';

    const AUCTION_HOUSE_PROXY_NONCE_OFFSET = 7;
    const GOVERNOR_N_DELEGATOR_NONCE_OFFSET = 10;

    const [deployer] = await ethers.getSigners();
    const nonce = await deployer.getTransactionCount();
    const expectedNounsDAOProxyAddress = ethers.utils.getContractAddress({
      from: deployer.address,
      nonce: nonce + GOVERNOR_N_DELEGATOR_NONCE_OFFSET,
    });
    const expectedAuctionHouseProxyAddress = ethers.utils.getContractAddress({
      from: deployer.address,
      nonce: nonce + AUCTION_HOUSE_PROXY_NONCE_OFFSET,
    });
    const contracts: Record<LocalContractName, Contract> = {
      WETH: {},
      NFTDescriptor: {},
      NounsDescriptor: {
        libraries: () => ({
          NFTDescriptor: contracts.NFTDescriptor.instance?.address as string,
        }),
      },
      NounsSeeder: {},
      NounsToken: {
        args: [
          args.noundersdao || deployer.address,
          expectedAuctionHouseProxyAddress,
          () => contracts.NounsDescriptor.instance?.address,
          () => contracts.NounsSeeder.instance?.address,
          proxyRegistryAddress,
        ],
      },
      NounsAuctionHouse: {
        waitForConfirmation: true,
      },
      NounsAuctionHouseProxyAdmin: {},
      NounsAuctionHouseProxy: {
        args: [
          () => contracts.NounsAuctionHouse.instance?.address,
          () => contracts.NounsAuctionHouseProxyAdmin.instance?.address,
          () =>
            new Interface(NounsAuctionHouseABI).encodeFunctionData('initialize', [
              contracts.NounsToken.instance?.address,
              contracts.WETH.instance?.address,
              args.auctionTimeBuffer,
              args.auctionReservePrice,
              args.auctionMinIncrementBidPercentage,
              args.auctionDuration,
            ]),
        ],
      },
      NounsDAOExecutor: {
        args: [expectedNounsDAOProxyAddress, args.timelockDelay],
      },
      NounsDAOLogicV1: {
        waitForConfirmation: true,
      },
      NounsDAOProxy: {
        args: [
          () => contracts.NounsDAOExecutor.instance?.address,
          () => contracts.NounsToken.instance?.address,
          args.noundersdao || deployer.address,
          () => contracts.NounsDAOExecutor.instance?.address,
          () => contracts.NounsDAOLogicV1.instance?.address,
          args.votingPeriod,
          args.votingDelay,
          args.proposalThresholdBps,
          args.quorumVotesBps,
        ],
      },
    };

    for (const [name, contract] of Object.entries(contracts)) {
      const factory = await ethers.getContractFactory(name, {
        libraries: contract?.libraries?.(),
      });

      const deployedContract = await factory.deploy(
        ...(contract.args?.map(a => (typeof a === 'function' ? a() : a)) ?? []),
      );

      if (contract.waitForConfirmation) {
        await deployedContract.deployed();
      }

      contracts[name as ContractName].instance = deployedContract;

      console.log(`${name} contract deployed to ${deployedContract.address}`);
    }

    return contracts;
  });
Example #19
Source File: test-precompile-democracy.ts    From moonbeam with GNU General Public License v3.0 4 votes vote down vote up
// When forgetting to call notePreimage, all following steps should work as intended
// until the end where the proposal is never enacted

describeDevMoonbeam("Democracy - forget notePreimage", (context) => {
  let genesisAccount: KeyringPair, alith: KeyringPair;
  let encodedHash: string;
  let enactmentPeriod, votingPeriod;
  let iFace: Interface;

  before("Setup genesis account for substrate", async () => {
    const keyring = new Keyring({ type: "ethereum" });
    genesisAccount = await keyring.addFromUri(GENESIS_ACCOUNT_PRIVATE_KEY, null, "ethereum");
    alith = await keyring.addFromUri(ALITH_PRIV_KEY, null, "ethereum");

    iFace = await deployAndInterfaceContract(context, "Democracy");
    // notePreimage
    // compute proposal hash but don't submit it
    const encodedProposal =
      context.polkadotApi.tx.parachainStaking
        .setParachainBondAccount(GENESIS_ACCOUNT)
        .method.toHex() || "";
    encodedHash = blake2AsHex(encodedProposal);
  });
  it("vote", async function () {
    this.timeout(200000);

    // propose
    const { events: eventsPropose } = await createBlockWithExtrinsic(
      context,
      genesisAccount,
      context.polkadotApi.tx.democracy.propose(encodedHash, PROPOSAL_AMOUNT)
    );
    expect(eventsPropose[2].toHuman().method).to.eq("Proposed");
    expect(eventsPropose[5].toHuman().method).to.eq("ExtrinsicSuccess");
    await context.createBlock();
    // second
    const { events: eventsSecond } = await createBlockWithExtrinsic(
      context,
      alith,
      context.polkadotApi.tx.democracy.second(0, 1000)
    );
    expect(eventsSecond[2].toHuman().method).to.eq("Seconded");
    expect(eventsSecond[5].toHuman().method).to.eq("ExtrinsicSuccess");
    // let Launchperiod elapse to turn the proposal into a referendum
    // launchPeriod minus the 3 blocks that already elapsed
    for (let i = 0; i < 7200; i++) {
      await context.createBlock();
    }
    // referendumCount
    let referendumCount = await context.polkadotApi.query.democracy.referendumCount();
    expect(referendumCount.toHuman()).to.equal("1");

    // vote
    await context.createBlock();
    const { events: eventsVote } = await createBlockWithExtrinsic(
      context,
      alith,
      context.polkadotApi.tx.democracy.vote(0, {
        Standard: { balance: VOTE_AMOUNT, vote: { aye: true, conviction: 1 } },
      })
    );

    expect(eventsVote[1].toHuman().method).to.eq("Voted");
    expect(eventsVote[4].toHuman().method).to.eq("ExtrinsicSuccess");

    // referendumInfoOf
    const referendumInfoOf = await context.polkadotApi.query.democracy.referendumInfoOf(0);
    expect((referendumInfoOf.toHuman() as any).Ongoing.proposalHash).to.equal(encodedHash);
    expect((referendumInfoOf.toHuman() as any).Ongoing.tally.ayes).to.equal(
      "10,000,000,000,000,000,000"
    );
    expect((referendumInfoOf.toHuman() as any).Ongoing.tally.turnout).to.equal(
      "10,000,000,000,000,000,000"
    );

    // let votePeriod + enactmentPeriod elapse to turn the proposal into a referendum
    for (let i = 0; i < Number(votingPeriod) + Number(enactmentPeriod); i++) {
      await context.createBlock();
    }
    // the enactement should fail
    let parachainBondInfo = await context.polkadotApi.query.parachainStaking.parachainBondInfo();
    expect(parachainBondInfo.toHuman()["account"]).to.equal(ZERO_ADDRESS);
  });
});
Example #20
Source File: tx.test.ts    From bodhi.js with Apache License 2.0 4 votes vote down vote up
describe('transaction tests', () => {
  const endpoint = process.env.ENDPOINT_URL || 'ws://127.0.0.1:9944';
  const provider = EvmRpcProvider.from(endpoint);

  const account1 = evmAccounts[0];
  const account2 = evmAccounts[1];
  const account3 = evmAccounts[2];
  const account4 = evmAccounts[3];
  const wallet1 = new Wallet(account1.privateKey).connect(provider as any);
  const wallet2 = new Wallet(account2.privateKey).connect(provider as any);
  const wallet3 = new Wallet(account3.privateKey).connect(provider as any);
  const wallet4 = new Wallet(account4.privateKey).connect(provider as any);

  let chainId: number;
  let storageByteDeposit: bigint;
  let txFeePerGas: bigint;
  let txGasLimit: BigNumber;
  let txGasPrice: BigNumber;

  before('prepare common variables', async () => {
    await provider.isReady();

    chainId = await provider.chainId();
    storageByteDeposit = (provider.api.consts.evm.storageDepositPerByte as UInt).toBigInt();
    txFeePerGas = (provider.api.consts.evm.txFeePerGas as UInt).toBigInt();

    ({ txGasLimit, txGasPrice } = calcEthereumTransactionParams({
      gasLimit: 2100001n,
      validUntil: 360001n,
      storageLimit: 64001n,
      txFeePerGas,
      storageByteDeposit
    }));
  });

  after('clean up', async () => {
    await provider.disconnect();
  });

  describe('test eth gas', () => {
    it('getEthResources', async () => {
      const randomWallet = Wallet.createRandom().connect(provider);

      const amount = '1000000000000000000';
      const resources = await provider.getEthResources({
        type: 0,
        from: wallet3.address,
        to: randomWallet.address,
        value: BigNumber.from(amount)
      });

      await wallet3.sendTransaction({
        type: 0,
        to: randomWallet.address,
        value: BigNumber.from(amount),
        ...resources
      });

      expect((await randomWallet.getBalance()).toString()).eq(amount);
    });

    it('getPrice', async () => {
      const randomWallet = Wallet.createRandom().connect(provider);

      const amount = '1000000000000000000';

      const params = await wallet3.populateTransaction({
        type: 0,
        to: randomWallet.address,
        value: BigNumber.from(amount)
      });

      const data = provider.validSubstrateResources({
        gasLimit: params.gasLimit,
        gasPrice: params.gasPrice
      });

      console.log({
        gasLimit: data.gasLimit.toString(),
        storageLimit: data.storageLimit.toString(),
        validUntil: data.validUntil.toString()
      });

      // expect((await randomWallet.getBalance()).toString()).eq(amount);
    });
  });

  describe('test the error tx', () => {
    it('InvalidDecimals', async () => {
      await expect(
        wallet1.sendTransaction({
          type: 0,
          to: wallet2.address,
          value: 1000001,
          gasLimit: txGasLimit,
          gasPrice: txGasPrice
        })
      ).to.be.rejectedWith('InvalidDecimals');
    });

    it('OutOfFund', async () => {
      await expect(
        wallet1.sendTransaction({
          type: 0,
          to: wallet2.address,
          value: 1000000000n * 10n ** 18n,
          gasLimit: txGasLimit,
          gasPrice: txGasPrice
        })
      ).to.be.rejectedWith('OutOfFund');
    });

    it('ExistentialDeposit', async () => {
      await expect(
        wallet3.sendTransaction({
          type: 0,
          to: Wallet.createRandom().address,
          value: 1000000,
          gasLimit: txGasLimit,
          gasPrice: txGasPrice
        })
      ).to.be.rejectedWith('ExistentialDeposit');
    });
  });

  describe('test deploy contract (hello world)', () => {
    const deployHelloWorldData =
      '0x60806040526040518060400160405280600c81526020017f48656c6c6f20576f726c642100000000000000000000000000000000000000008152506000908051906020019061004f929190610062565b5034801561005c57600080fd5b50610166565b82805461006e90610134565b90600052602060002090601f01602090048101928261009057600085556100d7565b82601f106100a957805160ff19168380011785556100d7565b828001600101855582156100d7579182015b828111156100d65782518255916020019190600101906100bb565b5b5090506100e491906100e8565b5090565b5b808211156101015760008160009055506001016100e9565b5090565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000600282049050600182168061014c57607f821691505b602082108114156101605761015f610105565b5b50919050565b61022e806101756000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063c605f76c14610030575b600080fd5b61003861004e565b6040516100459190610175565b60405180910390f35b6000805461005b906101c6565b80601f0160208091040260200160405190810160405280929190818152602001828054610087906101c6565b80156100d45780601f106100a9576101008083540402835291602001916100d4565b820191906000526020600020905b8154815290600101906020018083116100b757829003601f168201915b505050505081565b600081519050919050565b600082825260208201905092915050565b60005b838110156101165780820151818401526020810190506100fb565b83811115610125576000848401525b50505050565b6000601f19601f8301169050919050565b6000610147826100dc565b61015181856100e7565b93506101618185602086016100f8565b61016a8161012b565b840191505092915050565b6000602082019050818103600083015261018f818461013c565b905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b600060028204905060018216806101de57607f821691505b602082108114156101f2576101f1610197565b5b5091905056fea26469706673582212204d363ed34111d1be492d4fd086e9f2df62b3c625e89ade31f30e63201ed1e24f64736f6c63430008090033';

    let partialDeployTx;

    before(() => {
      partialDeployTx = {
        chainId,
        gasLimit: txGasLimit,
        gasPrice: txGasPrice,
        data: deployHelloWorldData,
        value: BigNumber.from(0)
      };
    });

    describe('with wallet', () => {
      it('succeeds', async () => {
        const response = await wallet1.sendTransaction({
          ...partialDeployTx,
          type: 0
        });
        const receipt = await response.wait(0);

        expect(receipt.type).equal(0);
        expect(receipt.status).equal(1);
        expect(receipt.from).equal(wallet1.address);
      });
    });

    describe('with legacy EIP-155 signature', () => {
      it('serialize, parse, and send tx correctly', async () => {
        const unsignedTx: AcalaEvmTX = {
          ...partialDeployTx,
          nonce: await wallet1.getTransactionCount()
        };

        const rawTx = await wallet1.signTransaction(unsignedTx);
        const parsedTx = parseTransaction(rawTx);

        expect(parsedTx.gasPrice.eq(txGasPrice)).equal(true);
        expect(parsedTx.gasLimit.eq(txGasLimit)).equal(true);

        expect(parsedTx.from).equal(wallet1.address);
        expect(parsedTx.data).equal(deployHelloWorldData);
        expect(parsedTx.type).equal(null);
        expect(parsedTx.maxPriorityFeePerGas).equal(undefined);
        expect(parsedTx.maxFeePerGas).equal(undefined);

        const response = await provider.sendTransaction(rawTx);
        const receipt = await response.wait(0);

        expect(receipt.type).equal(0); // TODO: should be null, need to fix getPartialTransactionReceipt
        expect(receipt.status).equal(1);
        expect(receipt.from).equal(wallet1.address);
      });
    });

    describe('with EIP-1559 signature', () => {
      it('serialize, parse, and send tx correctly', async () => {
        const priorityFee = BigNumber.from(2);
        const unsignedTx: AcalaEvmTX = {
          ...partialDeployTx,
          nonce: await wallet1.getTransactionCount(),
          gasPrice: undefined,
          maxPriorityFeePerGas: priorityFee,
          maxFeePerGas: txGasPrice,
          type: 2
        };

        const rawTx = await wallet1.signTransaction(unsignedTx);
        const parsedTx = parseTransaction(rawTx);

        expect(parsedTx.maxFeePerGas.eq(txGasPrice)).equal(true);
        expect(parsedTx.maxPriorityFeePerGas.eq(priorityFee)).equal(true);
        expect(parsedTx.gasLimit.eq(txGasLimit)).equal(true);

        expect(parsedTx.from).equal(wallet1.address);
        expect(parsedTx.data).equal(deployHelloWorldData);
        expect(parsedTx.type).equal(2);
        expect(parsedTx.gasPrice).equal(null);

        const response = await provider.sendTransaction(rawTx);
        const receipt = await response.wait(0);

        expect(receipt.type).equal(0); // TODO: should be 2, need to fix getPartialTransactionReceipt
        expect(receipt.status).equal(1);
        expect(receipt.from).equal(wallet1.address);
      });
    });

    describe('with EIP-712 signature', () => {
      let rawTx1: string;
      let rawTx2: string;

      it('serialize, parse, and send tx correctly', async () => {
        const gasLimit = BigNumber.from('210000');
        const validUntil = 10000;
        const storageLimit = 100000;

        const unsignEip712Tx: AcalaEvmTX = {
          ...partialDeployTx,
          nonce: await wallet1.getTransactionCount(),
          salt: provider.genesisHash,
          gasLimit,
          validUntil,
          storageLimit,
          type: 0x60,
          accessList: []
        };

        const sig = signTransaction(account1.privateKey, unsignEip712Tx);
        rawTx1 = serializeTransaction(unsignEip712Tx, sig);
        const parsedTx = parseTransaction(rawTx1);

        expect(parsedTx.gasLimit.eq(gasLimit)).equal(true);
        expect(parsedTx.validUntil.eq(validUntil)).equal(true);
        expect(parsedTx.storageLimit.eq(storageLimit)).equal(true);

        expect(parsedTx.from).equal(wallet1.address);
        expect(parsedTx.data).equal(deployHelloWorldData);
        expect(parsedTx.type).equal(96);
        expect(parsedTx.maxPriorityFeePerGas).equal(undefined);
        expect(parsedTx.maxFeePerGas).equal(undefined);
      });

      it('eip712 tip', async () => {
        const gasLimit = BigNumber.from('210000');
        const validUntil = 10000;
        const storageLimit = 100000;

        const unsignEip712Tx: AcalaEvmTX = {
          ...partialDeployTx,
          nonce: (await wallet1.getTransactionCount()) + 1,
          salt: provider.genesisHash,
          gasLimit,
          validUntil,
          storageLimit,
          tip: 2,
          type: 0x60,
          accessList: []
        };

        const sig = signTransaction(account1.privateKey, unsignEip712Tx);
        rawTx2 = serializeTransaction(unsignEip712Tx, sig);
        const parsedTx = parseTransaction(rawTx2);

        expect(parsedTx.gasLimit.eq(gasLimit)).equal(true);
        expect(parsedTx.validUntil.eq(validUntil)).equal(true);
        expect(parsedTx.storageLimit.eq(storageLimit)).equal(true);

        expect(parsedTx.tip.eq(2)).equal(true);
        expect(parsedTx.from).equal(wallet1.address);
        expect(parsedTx.data).equal(deployHelloWorldData);
        expect(parsedTx.type).equal(96);
        expect(parsedTx.maxPriorityFeePerGas).equal(undefined);
        expect(parsedTx.maxFeePerGas).equal(undefined);
      });

      it('send eip712 tx', async () => {
        await provider.sendTransaction(rawTx1);
        await provider.sendTransaction(rawTx2);
      });
    });
  });

  describe('test call contract (transfer ACA)', () => {
    const ACADigits = provider.api.registry.chainDecimals[0];
    const acaContract = new Contract(ADDRESS.ACA, ACAABI.abi, wallet1);
    const iface = new Interface(ACAABI.abi);
    const queryBalance = (addr) => acaContract.balanceOf(addr) as BigNumber;
    const transferAmount = parseUnits('100', ACADigits);
    let partialTransferTX: any;

    before(() => {
      partialTransferTX = {
        chainId,
        to: ADDRESS.ACA,
        gasLimit: txGasLimit,
        gasPrice: txGasPrice,
        data: iface.encodeFunctionData('transfer', [account2.evmAddress, transferAmount]),
        value: BigNumber.from(0)
      };
    });

    it('evm address match', () => {
      expect(computeDefaultSubstrateAddress(account1.evmAddress)).to.equal(account1.defaultSubstrateAddress);
      expect(computeDefaultSubstrateAddress(account2.evmAddress)).to.equal(account2.defaultSubstrateAddress);
    });

    describe('with provider', () => {
      it('has correct balance after transfer', async () => {
        const pairs = createTestPairs();
        const alice = pairs.alice;

        const oneAca = 10n ** BigInt(ACADigits);
        const amount = 1000n * oneAca;
        const balance = await queryBalance(account1.evmAddress);

        const extrinsic = provider.api.tx.balances.transfer(account1.defaultSubstrateAddress, amount);
        await extrinsic.signAsync(alice);
        await sendTx(provider.api, extrinsic);

        const _balance = await queryBalance(account1.evmAddress);
        expect(_balance.sub(balance).toBigInt()).equal(amount);
      });
    });

    describe('with legacy EIP-155 signature', () => {
      it('has correct balance after transfer', async () => {
        const balance1 = await queryBalance(account1.evmAddress);
        const balance2 = await queryBalance(account2.evmAddress);

        const transferTX: AcalaEvmTX = {
          ...partialTransferTX,
          nonce: await wallet1.getTransactionCount()
        };

        const rawTx = await wallet1.signTransaction(transferTX);
        const parsedTx = parseTransaction(rawTx);

        const response = await provider.sendTransaction(rawTx);
        const receipt = await response.wait(0);

        const _balance1 = await queryBalance(account1.evmAddress);
        const _balance2 = await queryBalance(account2.evmAddress);

        // TODO: check sender's balance is correct
        // expect(balance1.sub(_balance1).toNumber()).equal(transferAmount.toNumber() + gasUsed);
        expect(_balance2.sub(balance2).toNumber()).equal(transferAmount.toNumber());
      });
    });

    describe('with EIP-1559 signature', () => {
      it('has correct balance after transfer', async () => {
        const balance1 = await queryBalance(account1.evmAddress);
        const balance2 = await queryBalance(account2.evmAddress);

        const priorityFee = BigNumber.from(2);
        const transferTX: AcalaEvmTX = {
          ...partialTransferTX,
          nonce: await wallet1.getTransactionCount(),
          gasPrice: undefined,
          maxPriorityFeePerGas: priorityFee,
          maxFeePerGas: txGasPrice,
          type: 2
        };

        const rawTx = await wallet1.signTransaction(transferTX);
        const parsedTx = parseTransaction(rawTx);

        const response = await provider.sendTransaction(rawTx);
        const receipt = await response.wait(0);

        const _balance1 = await queryBalance(account1.evmAddress);
        const _balance2 = await queryBalance(account2.evmAddress);

        // TODO: check sender's balance is correct
        // expect(balance1.sub(_balance1).toNumber()).equal(transferAmount.toNumber() + gasUsed);
        expect(_balance2.sub(balance2).toNumber()).equal(transferAmount.toNumber());
      });
    });

    describe('with EIP-712 signature', () => {
      it('has correct balance after transfer', async () => {
        const balance1 = await queryBalance(account1.evmAddress);
        const balance2 = await queryBalance(account2.evmAddress);

        const gasLimit = BigNumber.from('210000');
        const validUntil = 10000;
        const storageLimit = 100000;

        const transferTX: AcalaEvmTX = {
          ...partialTransferTX,
          nonce: await wallet1.getTransactionCount(),
          salt: provider.genesisHash,
          gasLimit,
          validUntil,
          storageLimit,
          type: 0x60,
          accessList: []
        };

        const sig = signTransaction(account1.privateKey, transferTX);
        const rawTx = serializeTransaction(transferTX, sig);
        const parsedTx = parseTransaction(rawTx);

        await provider.sendTransaction(rawTx);

        const _balance1 = await queryBalance(account1.evmAddress);
        const _balance2 = await queryBalance(account2.evmAddress);

        // TODO: check sender's balance is correct
        // expect(balance1.sub(_balance1).toNumber()).equal(transferAmount.toNumber() + gasUsed);
        expect(_balance2.sub(balance2).toNumber()).equal(transferAmount.toNumber());
      });
    });
  });
});
Example #21
Source File: test-precompile-democracy.ts    From moonbeam with GNU General Public License v3.0 4 votes vote down vote up
describeDevMoonbeam("Democracy - second proposal", (context) => {
  let genesisAccount: KeyringPair, alith: KeyringPair;
  let encodedHash: `0x${string}`;
  let launchPeriod;
  let iFace: Interface;

  before("Setup genesis account for substrate", async () => {
    const keyring = new Keyring({ type: "ethereum" });
    genesisAccount = await keyring.addFromUri(GENESIS_ACCOUNT_PRIVATE_KEY, null, "ethereum");
    alith = await keyring.addFromUri(ALITH_PRIV_KEY, null, "ethereum");
    iFace = await deployAndInterfaceContract(context, "Democracy");

    //launchPeriod
    launchPeriod = await context.polkadotApi.consts.democracy.launchPeriod;

    // notePreimage
    encodedHash = await notePreimagePrecompile(
      context,
      iFace,
      context.polkadotApi.tx.parachainStaking.setParachainBondAccount(GENESIS_ACCOUNT)
    );

    // propose
    await sendPrecompileTx(
      context,
      ADDRESS_DEMO_PRECOMPILE,
      SELECTORS,
      GENESIS_ACCOUNT,
      GENESIS_ACCOUNT_PRIVATE_KEY,
      "propose",
      [encodedHash, numberToHex(Number(PROPOSAL_AMOUNT))]
    );

    // second
    await sendPrecompileTx(
      context,
      ADDRESS_DEMO_PRECOMPILE,
      SELECTORS,
      ALITH,
      ALITH_PRIV_KEY,
      "second",
      [numberToHex(0), numberToHex(1000)]
    );
  });
  // TODO: test getters
  it("second proposal", async function () {
    // publicProps
    const publicProps = await context.polkadotApi.query.democracy.publicProps();
    // encodedHash
    expect((publicProps.toHuman() as any)[0][1]).to.equal(encodedHash);
    // prop author
    expect((publicProps.toHuman() as any)[0][2]).to.equal(GENESIS_ACCOUNT);

    // depositOf
    const depositOf = await context.polkadotApi.query.democracy.depositOf(0);
    expect((depositOf.toHuman() as any)[1]).to.equal("1,000,000,000,000,000,000,000");
    expect((depositOf.toHuman() as any)[0][1]).to.equal(ALITH);
  });
  it("check launch period", async function () {
    // launchPeriod
    expect(launchPeriod.toHuman()).to.equal("7,200");
  });
  it("check referendum is up", async function () {
    this.timeout(1000000);
    // let Launchperiod elapse to turn the proposal into a referendum
    // launchPeriod minus the 3 blocks that already elapsed
    for (let i = 0; i < Number(launchPeriod) - 3; i++) {
      await context.createBlock();
    }
    // referendumCount
    let referendumCount = await context.polkadotApi.query.democracy.referendumCount();
    expect(referendumCount.toHuman()).to.equal("1");

    // publicPropCount
    const publicPropCount = await context.polkadotApi.query.democracy.publicPropCount();
    expect(publicPropCount.toHuman()).to.equal("1");

    // referendumInfoOf
    const referendumInfoOf = await context.polkadotApi.query.democracy.referendumInfoOf(0);
    expect((referendumInfoOf.toHuman() as any).Ongoing.proposalHash).to.equal(encodedHash);
  });
});
Example #22
Source File: endpoint.test.ts    From bodhi.js with Apache License 2.0 4 votes vote down vote up
describe('eth_sendRawTransaction', () => {
  const eth_sendRawTransaction = rpcGet('eth_sendRawTransaction');
  const eth_getTransactionCount = rpcGet('eth_getTransactionCount');
  const eth_getBalance = rpcGet('eth_getBalance');
  const eth_chainId = rpcGet('eth_chainId');
  const eth_gasPrice = rpcGet('eth_gasPrice');
  const eth_estimateGas = rpcGet('eth_estimateGas');

  const account1 = evmAccounts[0];
  const account2 = evmAccounts[1];
  const wallet1 = new Wallet(account1.privateKey);

  let chainId: number;
  let txGasLimit: BigNumber;
  let txGasPrice: BigNumber;
  let genesisHash: string;

  let api: ApiPromise;

  before('prepare common variables', async () => {
    chainId = BigNumber.from((await eth_chainId()).data.result).toNumber();

    txGasLimit = BigNumber.from(34132001);
    txGasPrice = BigNumber.from(200786445289);

    const endpoint = process.env.ENDPOINT_URL || 'ws://127.0.0.1:9944';
    const wsProvider = new WsProvider(endpoint);
    api = await ApiPromise.create({ provider: wsProvider });

    genesisHash = api.genesisHash.toHex(); // TODO: why EIP-712 salt has to be genesis hash?
  });

  after(async () => {
    await api.disconnect();
  });

  describe('deploy contract (hello world)', () => {
    const deployHelloWorldData =
      '0x60806040526040518060400160405280600c81526020017f48656c6c6f20576f726c642100000000000000000000000000000000000000008152506000908051906020019061004f929190610062565b5034801561005c57600080fd5b50610166565b82805461006e90610134565b90600052602060002090601f01602090048101928261009057600085556100d7565b82601f106100a957805160ff19168380011785556100d7565b828001600101855582156100d7579182015b828111156100d65782518255916020019190600101906100bb565b5b5090506100e491906100e8565b5090565b5b808211156101015760008160009055506001016100e9565b5090565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000600282049050600182168061014c57607f821691505b602082108114156101605761015f610105565b5b50919050565b61022e806101756000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063c605f76c14610030575b600080fd5b61003861004e565b6040516100459190610175565b60405180910390f35b6000805461005b906101c6565b80601f0160208091040260200160405190810160405280929190818152602001828054610087906101c6565b80156100d45780601f106100a9576101008083540402835291602001916100d4565b820191906000526020600020905b8154815290600101906020018083116100b757829003601f168201915b505050505081565b600081519050919050565b600082825260208201905092915050565b60005b838110156101165780820151818401526020810190506100fb565b83811115610125576000848401525b50505050565b6000601f19601f8301169050919050565b6000610147826100dc565b61015181856100e7565b93506101618185602086016100f8565b61016a8161012b565b840191505092915050565b6000602082019050818103600083015261018f818461013c565b905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b600060028204905060018216806101de57607f821691505b602082108114156101f2576101f1610197565b5b5091905056fea26469706673582212204d363ed34111d1be492d4fd086e9f2df62b3c625e89ade31f30e63201ed1e24f64736f6c63430008090033';

    let partialDeployTx;

    before(() => {
      partialDeployTx = {
        chainId,
        gasLimit: txGasLimit,
        gasPrice: txGasPrice,
        data: deployHelloWorldData,
        value: BigNumber.from(0)
      };
    });

    describe('with legacy EIP-155 signature', () => {
      it('serialize, parse, and send tx correctly', async () => {
        const unsignedTx: AcalaEvmTX = {
          ...partialDeployTx,
          nonce: (await eth_getTransactionCount([wallet1.address, 'pending'])).data.result
        };

        const rawTx = await wallet1.signTransaction(unsignedTx);
        const parsedTx = parseTransaction(rawTx);

        expect(parsedTx.gasPrice.eq(txGasPrice)).equal(true);
        expect(parsedTx.gasLimit.eq(txGasLimit)).equal(true);

        expect(parsedTx.from).equal(wallet1.address);
        expect(parsedTx.data).equal(deployHelloWorldData);
        expect(parsedTx.type).equal(null);
        expect(parsedTx.maxPriorityFeePerGas).equal(undefined);
        expect(parsedTx.maxFeePerGas).equal(undefined);

        const res = await eth_sendRawTransaction([rawTx]);
        expect(res.status).to.equal(200);
        expect(res.data.error?.message).to.equal(undefined); // for TX error RPC will still return 200
      });
    });

    describe('with EIP-1559 signature', () => {
      it('serialize, parse, and send tx correctly', async () => {
        const priorityFee = BigNumber.from(2);
        const unsignedTx: AcalaEvmTX = {
          ...partialDeployTx,
          nonce: (await eth_getTransactionCount([wallet1.address, 'pending'])).data.result,
          gasPrice: undefined,
          maxPriorityFeePerGas: priorityFee,
          maxFeePerGas: txGasPrice,
          type: 2
        };

        const rawTx = await wallet1.signTransaction(unsignedTx);
        const parsedTx = parseTransaction(rawTx);

        expect(parsedTx.maxFeePerGas.eq(txGasPrice)).equal(true);
        expect(parsedTx.maxPriorityFeePerGas.eq(priorityFee)).equal(true);
        expect(parsedTx.gasLimit.eq(txGasLimit)).equal(true);

        expect(parsedTx.from).equal(wallet1.address);
        expect(parsedTx.data).equal(deployHelloWorldData);
        expect(parsedTx.type).equal(2);
        expect(parsedTx.gasPrice).equal(null);

        const res = await eth_sendRawTransaction([rawTx]);
        expect(res.status).to.equal(200);
        expect(res.data.error?.message).to.equal(undefined); // for TX error RPC will still return 200
      });
    });

    describe('with EIP-712 signature', () => {
      it('serialize, parse, and send tx correctly', async () => {
        const gasLimit = BigNumber.from('210000');
        const validUntil = 10000;
        const storageLimit = 100000;

        const unsignEip712Tx: AcalaEvmTX = {
          ...partialDeployTx,
          nonce: (await eth_getTransactionCount([wallet1.address, 'pending'])).data.result,
          salt: genesisHash,
          gasLimit,
          validUntil,
          storageLimit,
          type: 0x60
        };

        const sig = signTransaction(account1.privateKey, unsignEip712Tx);
        const rawTx = serializeTransaction(unsignEip712Tx, sig);
        const parsedTx = parseTransaction(rawTx);

        expect(parsedTx.gasLimit.eq(gasLimit)).equal(true);
        expect(parsedTx.validUntil.eq(validUntil)).equal(true);
        expect(parsedTx.storageLimit.eq(storageLimit)).equal(true);

        expect(parsedTx.from).equal(wallet1.address);
        expect(parsedTx.data).equal(deployHelloWorldData);
        expect(parsedTx.type).equal(96);
        expect(parsedTx.maxPriorityFeePerGas).equal(undefined);
        expect(parsedTx.maxFeePerGas).equal(undefined);

        const res = await eth_sendRawTransaction([rawTx]);
        expect(res.status).to.equal(200);
        expect(res.data.error?.message).to.equal(undefined); // for TX error RPC will still return 200
      });
    });
  });

  describe('call contract (transfer ACA)', () => {
    const ETHDigits = 18;
    const ACADigits = 12;
    const acaContract = new Contract(ADDRESS.ACA, TokenABI.abi, wallet1);
    const iface = new Interface(TokenABI.abi);
    const queryBalance = async (addr) =>
      BigNumber.from((await eth_getBalance([addr, 'latest'])).data.result).div(10 ** (ETHDigits - ACADigits));
    const transferAmount = parseUnits('100', ACADigits);
    let partialTransferTX: Partial<AcalaEvmTX>;

    before(() => {
      partialTransferTX = {
        chainId,
        to: ADDRESS.ACA,
        gasLimit: txGasLimit,
        gasPrice: txGasPrice,
        data: iface.encodeFunctionData('transfer', [account2.evmAddress, transferAmount]),
        value: BigNumber.from(0)
      };
    });

    describe('with legacy EIP-155 signature', () => {
      it('has correct balance after transfer', async () => {
        const balance1 = await queryBalance(account1.evmAddress);
        const balance2 = await queryBalance(account2.evmAddress);

        const transferTX: AcalaEvmTX = {
          ...partialTransferTX,
          nonce: (await eth_getTransactionCount([wallet1.address, 'pending'])).data.result
        };

        const rawTx = await wallet1.signTransaction(transferTX);

        const res = await eth_sendRawTransaction([rawTx]);
        expect(res.status).to.equal(200);
        expect(res.data.error?.message).to.equal(undefined); // for TX error RPC will still return 200

        const _balance1 = await queryBalance(account1.evmAddress);
        const _balance2 = await queryBalance(account2.evmAddress);

        // TODO: check gasUsed is correct
        // const gasUsed = balance1.sub(_balance1).sub(transferAmount).toBigInt();
        expect(_balance2.sub(balance2).toBigInt()).equal(transferAmount.toBigInt());
      });
    });

    describe('with EIP-1559 signature', () => {
      it('has correct balance after transfer', async () => {
        const balance1 = await queryBalance(account1.evmAddress);
        const balance2 = await queryBalance(account2.evmAddress);

        const priorityFee = BigNumber.from(2);
        const transferTX: AcalaEvmTX = {
          ...partialTransferTX,
          nonce: (await eth_getTransactionCount([wallet1.address, 'pending'])).data.result,
          gasPrice: undefined,
          maxPriorityFeePerGas: priorityFee,
          maxFeePerGas: txGasPrice,
          type: 2
        };

        const rawTx = await wallet1.signTransaction(transferTX);
        const parsedTx = parseTransaction(rawTx);

        const res = await eth_sendRawTransaction([rawTx]);
        expect(res.status).to.equal(200);
        expect(res.data.error?.message).to.equal(undefined); // for TX error RPC will still return 200

        const _balance1 = await queryBalance(account1.evmAddress);
        const _balance2 = await queryBalance(account2.evmAddress);

        // TODO: check gasUsed is correct
        // const gasUsed = balance1.sub(_balance1).sub(transferAmount).toBigInt();
        expect(_balance2.sub(balance2).toBigInt()).equal(transferAmount.toBigInt());
      });
    });

    describe('with EIP-712 signature', () => {
      it('has correct balance after transfer', async () => {
        const balance1 = await queryBalance(account1.evmAddress);
        const balance2 = await queryBalance(account2.evmAddress);

        const gasLimit = BigNumber.from('210000');
        const validUntil = 10000;
        const storageLimit = 100000;

        const transferTX: AcalaEvmTX = {
          ...partialTransferTX,
          nonce: (await eth_getTransactionCount([wallet1.address, 'pending'])).data.result,
          salt: genesisHash,
          gasLimit,
          validUntil,
          storageLimit,
          type: 0x60
        };

        const sig = signTransaction(account1.privateKey, transferTX);
        const rawTx = serializeTransaction(transferTX, sig);
        const parsedTx = parseTransaction(rawTx);

        const res = await eth_sendRawTransaction([rawTx]);
        expect(res.status).to.equal(200);
        expect(res.data.error?.message).to.equal(undefined); // for TX error RPC will still return 200

        const _balance1 = await queryBalance(account1.evmAddress);
        const _balance2 = await queryBalance(account2.evmAddress);

        // TODO: check gasUsed is correct
        // const gasUsed = balance1.sub(_balance1).sub(transferAmount).toBigInt();
        expect(_balance2.sub(balance2).toBigInt()).equal(transferAmount.toBigInt());
      });
    });
  });

  describe('MetaMask send native ACA token', () => {
    const ETHDigits = 18;
    const ACADigits = 12;
    const queryBalance = async (addr) => BigNumber.from((await eth_getBalance([addr, 'latest'])).data.result);
    const transferAmount = parseUnits('16.8668', ETHDigits);
    let partialNativeTransferTX: Partial<AcalaEvmTX>;

    const estimateGas = async (): Promise<{
      gasPrice: string;
      gasLimit: string;
    }> => {
      const gasPrice = (await eth_gasPrice([])).data.result;
      const gasLimit = (
        await eth_estimateGas([
          {
            from: account1.evmAddress,
            to: account2.evmAddress,
            value: transferAmount,
            data: null,
            gasPrice
          }
        ])
      ).data.result;

      return {
        gasPrice,
        gasLimit
      };
    };

    before(() => {
      partialNativeTransferTX = {
        chainId,
        to: account2.evmAddress,
        data: '0x',
        value: transferAmount
      };
    });

    describe('with legacy EIP-155 signature', () => {
      it('has correct balance after transfer', async () => {
        const balance1 = await queryBalance(account1.evmAddress);
        const balance2 = await queryBalance(account2.evmAddress);

        const transferTX: AcalaEvmTX = {
          ...partialNativeTransferTX,
          ...(await estimateGas()),
          nonce: (await eth_getTransactionCount([wallet1.address, 'pending'])).data.result
        };

        const rawTx = await wallet1.signTransaction(transferTX);

        const res = await eth_sendRawTransaction([rawTx]);
        expect(res.status).to.equal(200);
        expect(res.data.error?.message).to.equal(undefined); // for TX error RPC will still return 200

        const _balance1 = await queryBalance(account1.evmAddress);
        const _balance2 = await queryBalance(account2.evmAddress);

        // TODO: check gasUsed is correct
        // const gasUsed = balance1.sub(_balance1).sub(transferAmount).toBigInt();
        expect(_balance2.sub(balance2).toBigInt()).equal(transferAmount.toBigInt());
      });
    });

    describe('with EIP-1559 signature', () => {
      it('has correct balance after transfer', async () => {
        const balance1 = await queryBalance(account1.evmAddress);
        const balance2 = await queryBalance(account2.evmAddress);

        const priorityFee = BigNumber.from(2);
        const { gasPrice, gasLimit } = await estimateGas();
        const transferTX: AcalaEvmTX = {
          ...partialNativeTransferTX,
          gasLimit,
          nonce: (await eth_getTransactionCount([wallet1.address, 'pending'])).data.result,
          gasPrice: undefined,
          maxPriorityFeePerGas: priorityFee,
          maxFeePerGas: gasPrice,
          type: 2
        };

        const rawTx = await wallet1.signTransaction(transferTX);
        const parsedTx = parseTransaction(rawTx);

        const res = await eth_sendRawTransaction([rawTx]);
        expect(res.status).to.equal(200);
        expect(res.data.error?.message).to.equal(undefined); // for TX error RPC will still return 200

        const _balance1 = await queryBalance(account1.evmAddress);
        const _balance2 = await queryBalance(account2.evmAddress);

        // TODO: check gasUsed is correct
        // const gasUsed = balance1.sub(_balance1).sub(transferAmount).toBigInt();
        expect(_balance2.sub(balance2).toBigInt()).equal(transferAmount.toBigInt());
      });
    });

    describe('with EIP-712 signature', () => {
      // TODO: EIP-712 doesn't use ETH gasLimit and gasPrice, do we need to support it?
      it.skip('has correct balance after transfer', async () => {
        // const balance1 = await queryBalance(account1.evmAddress);
        // const balance2 = await queryBalance(account2.evmAddress);
        // const gasLimit = BigNumber.from('210000');
        // const validUntil = 10000;
        // const storageLimit = 100000;
        // const transferTX: AcalaEvmTX = {
        //   ...partialNativeTransferTX,
        //   ...(await estimateGas()),
        //   nonce: (await eth_getTransactionCount([wallet1.address, 'pending'])).data.result,
        //   salt: genesisHash,
        //   gasLimit,
        //   validUntil,
        //   storageLimit,
        //   type: 0x60
        // };
        // const sig = signTransaction(account1.privateKey, transferTX);
        // const rawTx = serializeTransaction(transferTX, sig);
        // const parsedTx = parseTransaction(rawTx);
        // const res = await eth_sendRawTransaction([rawTx]);
        // expect(res.status).to.equal(200);
        // expect(res.data.error?.message).to.equal(undefined); // for TX error RPC will still return 200
        // const _balance1 = await queryBalance(account1.evmAddress);
        // const _balance2 = await queryBalance(account2.evmAddress);
        // // TODO: check gasUsed is correct
        // const gasUsed = balance1.sub(_balance1).sub(transferAmount).toBigInt();
        // expect(_balance2.sub(balance2).toBigInt()).equal(transferAmount.toBigInt());
      });
    });
  });
});