From c626767ab1398b530bbf713fe8bf4cb07acd12f5 Mon Sep 17 00:00:00 2001 From: CedarMist <134699267+CedarMist@users.noreply.github.com> Date: Tue, 29 Aug 2023 08:58:14 +0100 Subject: [PATCH 01/15] contracts: add working subcall example for accounts.Transfer --- contracts/contracts/tests/SubcallTests.sol | 6 ++++ contracts/test/subcall.ts | 41 ++++++++++++++++------ 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/contracts/contracts/tests/SubcallTests.sol b/contracts/contracts/tests/SubcallTests.sol index cfb17d90..23e3fb98 100644 --- a/contracts/contracts/tests/SubcallTests.sol +++ b/contracts/contracts/tests/SubcallTests.sol @@ -7,6 +7,12 @@ import {Sapphire} from "../Sapphire.sol"; contract SubcallTests { event SubcallResult(uint64 status, bytes data); + constructor () + payable + { + + } + function testSubcall(string memory method, bytes memory data) external { uint64 status; diff --git a/contracts/test/subcall.ts b/contracts/test/subcall.ts index f53461cc..9ed26d9c 100644 --- a/contracts/test/subcall.ts +++ b/contracts/test/subcall.ts @@ -3,26 +3,45 @@ import { expect } from 'chai'; import { SubcallTests } from '../typechain-types/contracts/tests/SubcallTests'; import * as cborg from 'cborg'; -import { Decoded, bech32 } from 'bech32'; +import { parseEther } from 'ethers/lib/utils'; +import { BigNumberish } from 'ethers'; + +export function fromBigInt(bi: BigNumberish) : Uint8Array { + return ethers.utils.arrayify(ethers.utils.zeroPad(ethers.utils.hexlify(bi), 16)); +} describe('Subcall', () => { let contract: SubcallTests; - let alice: Decoded; before(async () => { - alice = bech32.decode('oasis1qrec770vrek0a9a5lcrv0zvt22504k68svq7kzve'); const factory = await ethers.getContractFactory('SubcallTests'); - contract = (await factory.deploy()) as SubcallTests; + contract = (await factory.deploy({value: parseEther('1.0')})) as SubcallTests; }); it('account.Transfer', async () => { - const addr = new Uint8Array(21); + const [owner] = await ethers.getSigners(); + const ownerAddr = await owner.getAddress(); + + // Convert Ethereum address to native bytes with version prefix (V1=0x00) + const ownerNativeAddr = ethers.utils.arrayify(ethers.utils.zeroPad(ownerAddr, 21)); + expect(ownerNativeAddr.length).eq(21); + expect(await contract.provider.getBalance(contract.address)).eq(parseEther('1')); + + // transfer balance-1 back to owner + const balance = await contract.provider.getBalance(contract.address); const transfer = cborg.encode({ - to: new Uint8Array(alice.words.slice(1)), - amount: 0n, + to: ownerNativeAddr, + amount: [fromBigInt(balance.sub(1)), new Uint8Array()] }); - const tx = await contract.testSubcall('account.Transfer', transfer); + + // Wait for transaction to be mined + const tx = await contract.testSubcall('accounts.Transfer', transfer); const receipt = await tx.wait(); - const event = receipt.events![0].args!; - expect(event.status).eq(3); - expect(event.data).eq('0x636f7265'); + + // Transfer is success with: status=0, data=null + const event = receipt.events![0].args! as unknown as {status:number, data:string}; + expect(event.status).eq(0); + expect(cborg.decode(ethers.utils.arrayify(event.data))).is.null; + + // Ensure contract only has 1 wei left + expect(await contract.provider.getBalance(contract.address)).eq(1); }); }); From f432b74ea156ac053dba51f214adca8649ed11fc Mon Sep 17 00:00:00 2001 From: CedarMist <134699267+CedarMist@users.noreply.github.com> Date: Tue, 29 Aug 2023 11:29:32 +0100 Subject: [PATCH 02/15] contracts: add Subcall.sol with accounts_Transfer method --- contracts/contracts/Subcall.sol | 44 ++++++++++++++++++++++ contracts/contracts/tests/SubcallTests.sol | 7 ++++ contracts/test/subcall.ts | 30 ++++++++++----- 3 files changed, 71 insertions(+), 10 deletions(-) create mode 100644 contracts/contracts/Subcall.sol diff --git a/contracts/contracts/Subcall.sol b/contracts/contracts/Subcall.sol new file mode 100644 index 00000000..e4584013 --- /dev/null +++ b/contracts/contracts/Subcall.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: Apache-2.0 + +pragma solidity ^0.8.0; + +library Subcall { + address internal constant SUBCALL = + 0x0100000000000000000000000000000000000103; + + /** + * Submit a native message to the Oasis runtime layer + * + * Messages which re-enter the EVM module are forbidden: evm.* + * + * @param method Native message type + * @param body CBOR encoded body + * @return status_code Result of call + * @return data CBOR encoded result + */ + function subcall(string memory method, bytes memory body) + internal + returns (uint64 status_code, bytes memory data) + { + (bool success, bytes memory tmp) = SUBCALL.call( + abi.encode(method, body) + ); + + require(success, "subcall"); + + (status_code, data) = abi.decode(tmp, (uint64, bytes)); + } + + function accounts_Transfer(address to, uint128 value) + internal + { + (uint64 status_code,) = subcall("accounts.Transfer", abi.encodePacked( + hex"a262746f5500", + to, + hex"66616d6f756e748250", + value, + hex"40" + )); + require( status_code == 0 ); + } +} diff --git a/contracts/contracts/tests/SubcallTests.sol b/contracts/contracts/tests/SubcallTests.sol index 23e3fb98..186d7f0a 100644 --- a/contracts/contracts/tests/SubcallTests.sol +++ b/contracts/contracts/tests/SubcallTests.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.0; import {Sapphire} from "../Sapphire.sol"; +import {Subcall} from "../Subcall.sol"; contract SubcallTests { event SubcallResult(uint64 status, bytes data); @@ -20,4 +21,10 @@ contract SubcallTests { emit SubcallResult(status, data); } + + function testAccountsTransfer (address to, uint128 value) + external + { + Subcall.accounts_Transfer(to, value); + } } diff --git a/contracts/test/subcall.ts b/contracts/test/subcall.ts index 9ed26d9c..684fd96d 100644 --- a/contracts/test/subcall.ts +++ b/contracts/test/subcall.ts @@ -12,29 +12,32 @@ export function fromBigInt(bi: BigNumberish) : Uint8Array { describe('Subcall', () => { let contract: SubcallTests; + let ownerAddr: string; + let ownerNativeAddr: Uint8Array; + before(async () => { const factory = await ethers.getContractFactory('SubcallTests'); contract = (await factory.deploy({value: parseEther('1.0')})) as SubcallTests; - }); - it('account.Transfer', async () => { + const [owner] = await ethers.getSigners(); - const ownerAddr = await owner.getAddress(); + ownerAddr = await owner.getAddress(); // Convert Ethereum address to native bytes with version prefix (V1=0x00) - const ownerNativeAddr = ethers.utils.arrayify(ethers.utils.zeroPad(ownerAddr, 21)); + ownerNativeAddr = ethers.utils.arrayify(ethers.utils.zeroPad(ownerAddr, 21)); expect(ownerNativeAddr.length).eq(21); + }); + + it('accounts.Transfer', async () => { expect(await contract.provider.getBalance(contract.address)).eq(parseEther('1')); - // transfer balance-1 back to owner + // transfer balance-1 back to owner, then wait for transaction to be mined const balance = await contract.provider.getBalance(contract.address); - const transfer = cborg.encode({ + const message = cborg.encode({ to: ownerNativeAddr, amount: [fromBigInt(balance.sub(1)), new Uint8Array()] }); - - // Wait for transaction to be mined - const tx = await contract.testSubcall('accounts.Transfer', transfer); - const receipt = await tx.wait(); + let tx = await contract.testSubcall('accounts.Transfer', message); + let receipt = await tx.wait(); // Transfer is success with: status=0, data=null const event = receipt.events![0].args! as unknown as {status:number, data:string}; @@ -43,5 +46,12 @@ describe('Subcall', () => { // Ensure contract only has 1 wei left expect(await contract.provider.getBalance(contract.address)).eq(1); + + // Transfer using the Subcall.accounts_Transfer method + tx = await contract.testAccountsTransfer(ownerAddr, 1); + receipt = await tx.wait(); + + // Ensure contract only no wei left + expect(await contract.provider.getBalance(contract.address)).eq(0); }); }); From 77b0c6c6a1b2c20589e6d9c8f6e66514277ca911 Mon Sep 17 00:00:00 2001 From: CedarMist <134699267+CedarMist@users.noreply.github.com> Date: Wed, 30 Aug 2023 21:11:13 +0100 Subject: [PATCH 03/15] contracts: added consensus functions to subcall --- contracts/contracts/Subcall.sol | 170 +++++++++++++++++++-- contracts/contracts/tests/SubcallTests.sol | 41 ++++- contracts/package.json | 2 +- contracts/test/subcall.ts | 115 +++++++++++++- pnpm-lock.yaml | 72 ++++++--- 5 files changed, 357 insertions(+), 43 deletions(-) diff --git a/contracts/contracts/Subcall.sol b/contracts/contracts/Subcall.sol index e4584013..37020293 100644 --- a/contracts/contracts/Subcall.sol +++ b/contracts/contracts/Subcall.sol @@ -2,10 +2,69 @@ pragma solidity ^0.8.0; +import {sha512_256, Sapphire} from "./Sapphire.sol"; + +type StakingPublicKey is bytes21; + +type StakingSecretKey is bytes32; + +library ConsensusUtils { + string constant private ADDRESS_V0_CONTEXT_IDENTIFIER = "oasis-core/address: staking"; + uint8 constant private ADDRESS_V0_CONTEXT_VERSION = 0; + + string constant internal V0_SECP256K1ETH_CONTEXT_IDENTIFIER = "oasis-runtime-sdk/address: secp256k1eth"; + uint8 constant internal V0_SECP256K1ETH_CONTEXT_VERSION = 0; + + function generateStakingAddress(bytes memory personalization) + internal view + returns (StakingPublicKey publicKey, StakingSecretKey secretKey) + { + bytes memory sk = Sapphire.randomBytes(32, personalization); + + (bytes memory pk,) = Sapphire.generateSigningKeyPair( + Sapphire.SigningAlg.Ed25519Oasis, sk); + + publicKey = StakingPublicKey.wrap(_stakingAddressFromPublicKey(bytes32(pk))); + + secretKey = StakingSecretKey.wrap(bytes32(sk)); + } + + function _stakingAddressFromPublicKey(bytes32 ed25519publicKey) + internal view + returns (bytes21) + { + return _addressFromData( + ADDRESS_V0_CONTEXT_IDENTIFIER, + ADDRESS_V0_CONTEXT_VERSION, + abi.encodePacked(ed25519publicKey)); + } + + function _addressFromData( + string memory contextIdentifier, + uint8 contextVersion, + bytes memory data + ) + internal view + returns (bytes21) + { + return bytes21( + abi.encodePacked( + contextVersion, + bytes20( + sha512_256( + abi.encodePacked( + contextIdentifier, + contextVersion, + data))))); + } +} + library Subcall { address internal constant SUBCALL = 0x0100000000000000000000000000000000000103; + error Subcall_Error (); + /** * Submit a native message to the Oasis runtime layer * @@ -24,21 +83,114 @@ library Subcall { abi.encode(method, body) ); - require(success, "subcall"); + if( false == success ) { + revert Subcall_Error(); + } (status_code, data) = abi.decode(tmp, (uint64, bytes)); } - function accounts_Transfer(address to, uint128 value) + + error ConsensusUndelegate_Error(uint64 status_code, bytes data); + + function consensus_Undelegate( + StakingPublicKey from, + uint128 shares + ) + internal + { + (uint64 status_code, bytes memory data) = subcall( + "consensus.Undelegate", + abi.encodePacked( + hex"a26466726f6d55", from, hex"6673686172657350", shares + )); + + if( status_code != 0 ) { + revert ConsensusUndelegate_Error(status_code, data); + } + } + + function _subcall_to_amount ( + string memory method, + StakingPublicKey to, + uint128 value + ) internal + returns (uint64 status_code, bytes memory data) { - (uint64 status_code,) = subcall("accounts.Transfer", abi.encodePacked( - hex"a262746f5500", - to, - hex"66616d6f756e748250", - value, - hex"40" + (status_code, data) = subcall(method, abi.encodePacked( + hex"a262746f55", to, hex"66616d6f756e748250", value, hex"40" )); - require( status_code == 0 ); + } + + + error ConsensusDelegate_Error(uint64 status_code, bytes data); + + function consensus_Delegate(StakingPublicKey to, uint128 value) + internal + { + (uint64 status_code, bytes memory data) = _subcall_to_amount("consensus.Delegate", to, value); + + if( status_code != 0 ) { + revert ConsensusDelegate_Error(status_code, data); + } + } + + + error ConsensusDeposit_Error(uint64 status_code, bytes data); + + /** + * Deposit into runtime call. + * + * Transfer from consensus staking to an account in this runtime. + * + * The transaction signer has a consensus layer allowance benefiting this + * runtime's staking address. + * + * @param to runtime account gets the tokens + * @param value native token amount + */ + function consensus_Deposit(StakingPublicKey to, uint128 value) + internal + { + (uint64 status_code, bytes memory data) = _subcall_to_amount("consensus.Deposit", to, value); + + if( status_code != 0 ) { + revert ConsensusDeposit_Error(status_code, data); + } + } + + + error ConsensusWithdraw_Error(uint64 status_code, bytes data); + + /** + * Withdraw from runtime call. + * + * Transfer from an account in this runtime to consensus staking. + * + * @param to consensus staking account which gets the tokens + * @param value native token amount + */ + function consensus_Withdraw(StakingPublicKey to, uint128 value) + internal + { + (uint64 status_code, bytes memory data) = _subcall_to_amount("consensus.Withdraw", to, value); + + if( status_code != 0 ) { + revert ConsensusDeposit_Error(status_code, data); + } + } + + + error AccountsTransfer_Error(uint64 status_code, bytes data); + + function accounts_Transfer(StakingPublicKey to, uint128 value) + internal + { + (uint64 status_code, bytes memory data) = _subcall_to_amount("accounts.Transfer", to, value); + + if( status_code != 0 ) { + revert AccountsTransfer_Error(status_code, data); + } } } diff --git a/contracts/contracts/tests/SubcallTests.sol b/contracts/contracts/tests/SubcallTests.sol index 186d7f0a..6b971a23 100644 --- a/contracts/contracts/tests/SubcallTests.sol +++ b/contracts/contracts/tests/SubcallTests.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; import {Sapphire} from "../Sapphire.sol"; -import {Subcall} from "../Subcall.sol"; +import {Subcall,ConsensusUtils,StakingPublicKey,StakingSecretKey} from "../Subcall.sol"; contract SubcallTests { event SubcallResult(uint64 status, bytes data); @@ -14,6 +14,19 @@ contract SubcallTests { } + receive () external payable + { } + + function generateRandomAddress () + external view + returns ( + StakingPublicKey publicKey, + StakingSecretKey secretKey + ) + { + return ConsensusUtils.generateStakingAddress(""); + } + function testSubcall(string memory method, bytes memory data) external { uint64 status; @@ -22,9 +35,33 @@ contract SubcallTests { emit SubcallResult(status, data); } - function testAccountsTransfer (address to, uint128 value) + function testAccountsTransfer (StakingPublicKey to, uint128 value) external { Subcall.accounts_Transfer(to, value); } + + function testConsensusDelegate (StakingPublicKey to, uint128 value) + external + { + Subcall.consensus_Delegate(to, value); + } + + function testConsensusUndelegate (StakingPublicKey to, uint128 value) + external + { + Subcall.consensus_Undelegate(to, value); + } + + function testConsensusDeposit (StakingPublicKey to, uint128 value) + external + { + Subcall.consensus_Deposit(to, value); + } + + function testConsensusWithdraw (StakingPublicKey to, uint128 value) + external + { + Subcall.consensus_Withdraw(to, value); + } } diff --git a/contracts/package.json b/contracts/package.json index 06ae7326..c7f2171e 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -27,6 +27,7 @@ "@nomicfoundation/hardhat-chai-matchers": "^1.0.5", "@nomiclabs/hardhat-ethers": "^2.1.1", "@oasisprotocol/sapphire-hardhat": "workspace:^", + "@oasisprotocol/client": "^0.1.1-alpha.2", "@openzeppelin/contracts": "^4.7.3", "@typechain/ethers-v5": "^10.1.0", "@typechain/hardhat": "^6.1.3", @@ -35,7 +36,6 @@ "@types/node": "^18.7.18", "@typescript-eslint/eslint-plugin": "^5.37.0", "@typescript-eslint/parser": "^5.37.0", - "bech32": "^2.0.0", "cborg": "^1.9.5", "chai": "^4.3.6", "eslint": "^8.23.1", diff --git a/contracts/test/subcall.ts b/contracts/test/subcall.ts index 684fd96d..40b764d7 100644 --- a/contracts/test/subcall.ts +++ b/contracts/test/subcall.ts @@ -1,34 +1,60 @@ import { ethers } from 'hardhat'; import { expect } from 'chai'; -import { SubcallTests } from '../typechain-types/contracts/tests/SubcallTests'; +import { BinaryLike, createHash } from 'crypto'; +import * as oasis from '@oasisprotocol/client'; import * as cborg from 'cborg'; - +import { SubcallTests } from '../typechain-types/contracts/tests/SubcallTests'; +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/dist/src/signer-with-address"; import { parseEther } from 'ethers/lib/utils'; -import { BigNumberish } from 'ethers'; +import { BigNumber, BigNumberish, Signer } from 'ethers'; -export function fromBigInt(bi: BigNumberish) : Uint8Array { +function fromBigInt(bi: BigNumberish) : Uint8Array { return ethers.utils.arrayify(ethers.utils.zeroPad(ethers.utils.hexlify(bi), 16)); } +async function ensureBalance(contract: SubcallTests, initialBalance:BigNumber, owner: SignerWithAddress) { + const balance = await contract.provider.getBalance(contract.address); + if (balance.lt(initialBalance)) { + const resp = await owner.sendTransaction({ + to: contract.address, + value: initialBalance.sub(balance), + data: "0x" + }); + await resp.wait(); + } + expect(await contract.provider.getBalance(contract.address)).eq(initialBalance); +} + describe('Subcall', () => { let contract: SubcallTests; + let owner: SignerWithAddress; let ownerAddr: string; let ownerNativeAddr: Uint8Array; + let kp: {publicKey: Uint8Array, secretKey: Uint8Array}; before(async () => { const factory = await ethers.getContractFactory('SubcallTests'); contract = (await factory.deploy({value: parseEther('1.0')})) as SubcallTests; - const [owner] = await ethers.getSigners(); + const signers = await ethers.getSigners(); + owner = signers[0]; ownerAddr = await owner.getAddress(); // Convert Ethereum address to native bytes with version prefix (V1=0x00) ownerNativeAddr = ethers.utils.arrayify(ethers.utils.zeroPad(ownerAddr, 21)); expect(ownerNativeAddr.length).eq(21); + + const rawKp = await contract.generateRandomAddress(); + kp = { + publicKey: ethers.utils.arrayify(rawKp.publicKey), + secretKey: ethers.utils.arrayify(rawKp.secretKey) + }; }); it('accounts.Transfer', async () => { - expect(await contract.provider.getBalance(contract.address)).eq(parseEther('1')); + // Ensure contract has an initial balance + const initialBalance = parseEther('1.0'); + await ensureBalance(contract, initialBalance, owner); // transfer balance-1 back to owner, then wait for transaction to be mined const balance = await contract.provider.getBalance(contract.address); @@ -48,10 +74,85 @@ describe('Subcall', () => { expect(await contract.provider.getBalance(contract.address)).eq(1); // Transfer using the Subcall.accounts_Transfer method - tx = await contract.testAccountsTransfer(ownerAddr, 1); + tx = await contract.testAccountsTransfer(ownerNativeAddr, 1); receipt = await tx.wait(); // Ensure contract only no wei left expect(await contract.provider.getBalance(contract.address)).eq(0); }); + + it('consensus.Delegate', async () => { + // Ensure contract has an initial balance + const initialBalance = parseEther('1.0'); + await ensureBalance(contract, initialBalance, owner); + + // Delegate 0, ensure balance does not change + let tx = await contract.testConsensusDelegate(kp.publicKey, 0); + await tx.wait(); + expect(await contract.provider.getBalance(contract.address)).eq(initialBalance); + + // Manually encode & submit consensus.Delegate message + const message = cborg.encode({ + to: kp.publicKey, + amount: [fromBigInt(0), new Uint8Array()] + }); + tx = await contract.testSubcall('consensus.Delegate', message); + let receipt = await tx.wait(); + + // Transfer is success with: status=0, data=null + const event = receipt.events![0].args! as unknown as {status:number, data:string}; + const decodedEvent = { + status: event.status, + data: event.status == 0 ? cborg.decode(ethers.utils.arrayify(event.data)) : new TextDecoder().decode(ethers.utils.arrayify(event.data)) + }; + expect(event.status).eq(0); + expect(cborg.decode(ethers.utils.arrayify(event.data))).is.null; + + // Ensure contract only no wei left + //expect(await contract.provider.getBalance(contract.address)).eq(0); + }); + + it('Derives Staking Addresses', async () => { + const newKeypair = await contract.generateRandomAddress(); + + // Verify @oasisprotocol/client matches Solidity + const alice = oasis.signature.NaclSigner.fromSeed( + ethers.utils.arrayify(newKeypair.secretKey), 'this key is not important', + ); + const computedPublicKey = ethers.utils.hexlify(await oasis.staking.addressFromPublicKey(alice.public())); + + expect(computedPublicKey).eq(ethers.utils.hexlify(newKeypair.publicKey)); + }); + + it('consensus.Undelegate', async () => { + // Ensure contract has an initial balance + const initialBalance = parseEther('1.0'); + await ensureBalance(contract, initialBalance, owner); + + let tx = await contract.testConsensusUndelegate(kp.publicKey, 0); + await tx.wait(); + expect(await contract.provider.getBalance(contract.address)).eq(initialBalance); + }); + + /* + it('consensus.Deposit', async () => { + // Ensure contract has an initial balance + const initialBalance = parseEther('1.0'); + await ensureBalance(contract, initialBalance, owner); + + let tx = await contract.testConsensusDeposit(kp.publicKey, 0); + await tx.wait(); + expect(await contract.provider.getBalance(contract.address)).eq(initialBalance); + }); + */ + + it('consensus.Withdraw', async () => { + // Ensure contract has an initial balance + const initialBalance = parseEther('1.0'); + await ensureBalance(contract, initialBalance, owner); + + let tx = await contract.testConsensusWithdraw(kp.publicKey, 0); + await tx.wait(); + expect(await contract.provider.getBalance(contract.address)).eq(initialBalance); + }); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 164c11cd..3688ad73 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -123,6 +123,9 @@ importers: '@nomiclabs/hardhat-ethers': specifier: ^2.1.1 version: 2.2.3(ethers@5.7.2)(hardhat@2.16.1) + '@oasisprotocol/client': + specifier: ^0.1.1-alpha.2 + version: 0.1.1-alpha.2 '@oasisprotocol/sapphire-hardhat': specifier: workspace:^ version: link:../integrations/hardhat @@ -150,9 +153,6 @@ importers: '@typescript-eslint/parser': specifier: ^5.37.0 version: 5.60.1(eslint@8.43.0)(typescript@4.9.5) - bech32: - specifier: ^2.0.0 - version: 2.0.0 cborg: specifier: ^1.9.5 version: 1.10.2 @@ -3892,6 +3892,18 @@ packages: - supports-color dev: true + /@oasisprotocol/client@0.1.1-alpha.2: + resolution: {integrity: sha512-iKFOjMvBWVs7eVSd+H/wWthuYeXXCcJZ0AxX4anLu+UQvmaXUzzbGwiM1YH3XkpJsYhrIDebU/L8WUr7C21LuA==} + dependencies: + bech32: 2.0.0 + bip39: 3.1.0 + cborg: 1.10.2 + grpc-web: 1.4.2 + js-sha512: 0.8.0 + protobufjs: 7.1.2 + tweetnacl: 1.0.3 + dev: true + /@oasisprotocol/deoxysii@0.0.5: resolution: {integrity: sha512-a6wYPjk8ALDIiQW/971AKOTSTY1qSdld+Y05F44gVZvlb3FOyHfgbIxXm7CZnUG1A+jK49g5SCWYP+V3/Tc75Q==} dependencies: @@ -3945,26 +3957,18 @@ packages: /@protobufjs/aspromise@1.1.2: resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} requiresBuild: true - dev: false - optional: true /@protobufjs/base64@1.1.2: resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} requiresBuild: true - dev: false - optional: true /@protobufjs/codegen@2.0.4: resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} requiresBuild: true - dev: false - optional: true /@protobufjs/eventemitter@1.1.0: resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} requiresBuild: true - dev: false - optional: true /@protobufjs/fetch@1.1.0: resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} @@ -3972,38 +3976,26 @@ packages: dependencies: '@protobufjs/aspromise': 1.1.2 '@protobufjs/inquire': 1.1.0 - dev: false - optional: true /@protobufjs/float@1.0.2: resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} requiresBuild: true - dev: false - optional: true /@protobufjs/inquire@1.1.0: resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} requiresBuild: true - dev: false - optional: true /@protobufjs/path@1.1.2: resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} requiresBuild: true - dev: false - optional: true /@protobufjs/pool@1.1.0: resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} requiresBuild: true - dev: false - optional: true /@protobufjs/utf8@1.1.0: resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} requiresBuild: true - dev: false - optional: true /@redux-saga/core@1.2.3: resolution: {integrity: sha512-U1JO6ncFBAklFTwoQ3mjAeQZ6QGutsJzwNBjgVLSWDpZTRhobUzuVDS1qH3SKGJD8fvqoaYOjp6XJ3gCmeZWgA==} @@ -6456,6 +6448,12 @@ packages: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} + /bip39@3.1.0: + resolution: {integrity: sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A==} + dependencies: + '@noble/hashes': 1.2.0 + dev: true + /blakejs@1.2.1: resolution: {integrity: sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==} @@ -10067,6 +10065,10 @@ packages: engines: {node: '>=4.x'} dev: true + /grpc-web@1.4.2: + resolution: {integrity: sha512-gUxWq42l5ldaRplcKb4Pw5O4XBONWZgz3vxIIXnfIeJj8Jc3wYiq2O4c9xzx/NGbbPEej4rhI62C9eTENwLGNw==} + dev: true + /gzip-size@6.0.0: resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==} engines: {node: '>=10'} @@ -12026,7 +12028,6 @@ packages: /js-sha512@0.8.0: resolution: {integrity: sha512-PWsmefG6Jkodqt+ePTvBZCSMFgN7Clckjd0O7su3I0+BW2QWUTJNzjktHsztGLhncP2h8mcF9V9Y2Ha59pAViQ==} - dev: false /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -12654,6 +12655,10 @@ packages: dev: false optional: true + /long@5.2.3: + resolution: {integrity: sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==} + dev: true + /loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -15057,6 +15062,25 @@ packages: engines: {node: '>= 8'} dev: true + /protobufjs@7.1.2: + resolution: {integrity: sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==} + engines: {node: '>=12.0.0'} + requiresBuild: true + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.4 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.0 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.0 + '@types/node': 18.16.18 + long: 5.2.3 + dev: true + /proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} From 43c6bf4def4b90f46c70f43fcd0330d615ad2645 Mon Sep 17 00:00:00 2001 From: CedarMist <134699267+CedarMist@users.noreply.github.com> Date: Wed, 30 Aug 2023 23:51:50 +0100 Subject: [PATCH 04/15] contracts: subcall documentation updates --- contracts/contracts/Subcall.sol | 121 +++++++++++++-------- contracts/contracts/tests/SubcallTests.sol | 2 +- contracts/test/subcall.ts | 27 +++-- 3 files changed, 88 insertions(+), 62 deletions(-) diff --git a/contracts/contracts/Subcall.sol b/contracts/contracts/Subcall.sol index 37020293..22a08f8e 100644 --- a/contracts/contracts/Subcall.sol +++ b/contracts/contracts/Subcall.sol @@ -8,6 +8,7 @@ type StakingPublicKey is bytes21; type StakingSecretKey is bytes32; + library ConsensusUtils { string constant private ADDRESS_V0_CONTEXT_IDENTIFIER = "oasis-core/address: staking"; uint8 constant private ADDRESS_V0_CONTEXT_VERSION = 0; @@ -59,6 +60,7 @@ library ConsensusUtils { } } + library Subcall { address internal constant SUBCALL = 0x0100000000000000000000000000000000000103; @@ -72,12 +74,12 @@ library Subcall { * * @param method Native message type * @param body CBOR encoded body - * @return status_code Result of call + * @return status Result of call * @return data CBOR encoded result */ function subcall(string memory method, bytes memory body) internal - returns (uint64 status_code, bytes memory data) + returns (uint64 status, bytes memory data) { (bool success, bytes memory tmp) = SUBCALL.call( abi.encode(method, body) @@ -87,110 +89,135 @@ library Subcall { revert Subcall_Error(); } - (status_code, data) = abi.decode(tmp, (uint64, bytes)); + (status, data) = abi.decode(tmp, (uint64, bytes)); + } + + + function _subcall_to_amount ( + string memory method, + StakingPublicKey to, + uint128 value + ) + internal + returns (uint64 status, bytes memory data) + { + (status, data) = subcall( + method, + abi.encodePacked( + hex"a262", "to", hex"55", to, + hex"66", "amount", hex"8250", value, hex"40" + )); } - error ConsensusUndelegate_Error(uint64 status_code, bytes data); + error ConsensusUndelegate_Error(uint64 status, string data); + /** + * Start the undelegation process of the given number of shares from + * consensus staking account to runtime account. + * + * @param from Public key which shares were delegated to + * @param shares Number of shares to withdraw back to us + */ function consensus_Undelegate( StakingPublicKey from, uint128 shares ) internal { - (uint64 status_code, bytes memory data) = subcall( + (uint64 status, bytes memory data) = subcall( "consensus.Undelegate", abi.encodePacked( - hex"a26466726f6d55", from, hex"6673686172657350", shares + hex"a264", "from", hex"55", from, + hex"66", "shares", hex"50", shares )); - if( status_code != 0 ) { - revert ConsensusUndelegate_Error(status_code, data); + if( status != 0 ) { + revert ConsensusUndelegate_Error(status, string(data)); } } - function _subcall_to_amount ( - string memory method, - StakingPublicKey to, - uint128 value - ) - internal - returns (uint64 status_code, bytes memory data) - { - (status_code, data) = subcall(method, abi.encodePacked( - hex"a262746f55", to, hex"66616d6f756e748250", value, hex"40" - )); - } + error ConsensusDelegate_Error(uint64 status, string data); - error ConsensusDelegate_Error(uint64 status_code, bytes data); - + /** + * Delegate native token to consensus level + * + * @param to Staking account + * @param value native token amount (in wei) + */ function consensus_Delegate(StakingPublicKey to, uint128 value) internal { - (uint64 status_code, bytes memory data) = _subcall_to_amount("consensus.Delegate", to, value); + (uint64 status, bytes memory data) = _subcall_to_amount("consensus.Delegate", to, value); - if( status_code != 0 ) { - revert ConsensusDelegate_Error(status_code, data); + if( status != 0 ) { + revert ConsensusDelegate_Error(status, string(data)); } } - error ConsensusDeposit_Error(uint64 status_code, bytes data); + error ConsensusDeposit_Error(uint64 status, bytes data); /** - * Deposit into runtime call. - * - * Transfer from consensus staking to an account in this runtime. + * Transfer from consensus staking account to an account in this runtime. * * The transaction signer has a consensus layer allowance benefiting this * runtime's staking address. * - * @param to runtime account gets the tokens - * @param value native token amount + * @param to runtime account which gets the tokens + * @param value native token amount (in wei) */ function consensus_Deposit(StakingPublicKey to, uint128 value) internal { - (uint64 status_code, bytes memory data) = _subcall_to_amount("consensus.Deposit", to, value); + (uint64 status, bytes memory data) = _subcall_to_amount("consensus.Deposit", to, value); - if( status_code != 0 ) { - revert ConsensusDeposit_Error(status_code, data); + if( status != 0 ) { + revert ConsensusDeposit_Error(status, data); } } - error ConsensusWithdraw_Error(uint64 status_code, bytes data); + error ConsensusWithdraw_Error(uint64 status, string data); /** - * Withdraw from runtime call. - * - * Transfer from an account in this runtime to consensus staking. + * Transfer from an account in this runtime to a consensus staking account. * * @param to consensus staking account which gets the tokens - * @param value native token amount + * @param value native token amount (in wei) */ function consensus_Withdraw(StakingPublicKey to, uint128 value) internal { - (uint64 status_code, bytes memory data) = _subcall_to_amount("consensus.Withdraw", to, value); + (uint64 status, bytes memory data) = _subcall_to_amount("consensus.Withdraw", to, value); - if( status_code != 0 ) { - revert ConsensusDeposit_Error(status_code, data); + if( status != 0 ) { + revert ConsensusWithdraw_Error(status, string(data)); } } - error AccountsTransfer_Error(uint64 status_code, bytes data); + error AccountsTransfer_Error(uint64 status, string data); - function accounts_Transfer(StakingPublicKey to, uint128 value) + /** + * Perform a transfer to another account + * + * This is equivalent of `payable(to).transfer(value);` + * + * @param to Destination account + * @param value native token amount (in wei) + */ + function accounts_Transfer(address to, uint128 value) internal { - (uint64 status_code, bytes memory data) = _subcall_to_amount("accounts.Transfer", to, value); + (uint64 status, bytes memory data) = _subcall_to_amount( + "accounts.Transfer", + StakingPublicKey.wrap(bytes21(abi.encodePacked(uint8(0x00), to))), + value); - if( status_code != 0 ) { - revert AccountsTransfer_Error(status_code, data); + if( status != 0 ) { + revert AccountsTransfer_Error(status, string(data)); } } } diff --git a/contracts/contracts/tests/SubcallTests.sol b/contracts/contracts/tests/SubcallTests.sol index 6b971a23..ee20e7a4 100644 --- a/contracts/contracts/tests/SubcallTests.sol +++ b/contracts/contracts/tests/SubcallTests.sol @@ -35,7 +35,7 @@ contract SubcallTests { emit SubcallResult(status, data); } - function testAccountsTransfer (StakingPublicKey to, uint128 value) + function testAccountsTransfer (address to, uint128 value) external { Subcall.accounts_Transfer(to, value); diff --git a/contracts/test/subcall.ts b/contracts/test/subcall.ts index 40b764d7..0e785992 100644 --- a/contracts/test/subcall.ts +++ b/contracts/test/subcall.ts @@ -1,6 +1,5 @@ import { ethers } from 'hardhat'; import { expect } from 'chai'; -import { BinaryLike, createHash } from 'crypto'; import * as oasis from '@oasisprotocol/client'; import * as cborg from 'cborg'; import { SubcallTests } from '../typechain-types/contracts/tests/SubcallTests'; @@ -51,6 +50,18 @@ describe('Subcall', () => { }; }); + it('Derive Staking Addresses', async () => { + const newKeypair = await contract.generateRandomAddress(); + + // Verify @oasisprotocol/client matches Solidity + const alice = oasis.signature.NaclSigner.fromSeed( + ethers.utils.arrayify(newKeypair.secretKey), 'this key is not important', + ); + const computedPublicKey = ethers.utils.hexlify(await oasis.staking.addressFromPublicKey(alice.public())); + + expect(computedPublicKey).eq(ethers.utils.hexlify(newKeypair.publicKey)); + }); + it('accounts.Transfer', async () => { // Ensure contract has an initial balance const initialBalance = parseEther('1.0'); @@ -74,7 +85,7 @@ describe('Subcall', () => { expect(await contract.provider.getBalance(contract.address)).eq(1); // Transfer using the Subcall.accounts_Transfer method - tx = await contract.testAccountsTransfer(ownerNativeAddr, 1); + tx = await contract.testAccountsTransfer(ownerAddr, 1); receipt = await tx.wait(); // Ensure contract only no wei left @@ -112,18 +123,6 @@ describe('Subcall', () => { //expect(await contract.provider.getBalance(contract.address)).eq(0); }); - it('Derives Staking Addresses', async () => { - const newKeypair = await contract.generateRandomAddress(); - - // Verify @oasisprotocol/client matches Solidity - const alice = oasis.signature.NaclSigner.fromSeed( - ethers.utils.arrayify(newKeypair.secretKey), 'this key is not important', - ); - const computedPublicKey = ethers.utils.hexlify(await oasis.staking.addressFromPublicKey(alice.public())); - - expect(computedPublicKey).eq(ethers.utils.hexlify(newKeypair.publicKey)); - }); - it('consensus.Undelegate', async () => { // Ensure contract has an initial balance const initialBalance = parseEther('1.0'); From e598a135481b6e794f8c290fe00ce9c8f41f8dc5 Mon Sep 17 00:00:00 2001 From: CedarMist <134699267+CedarMist@users.noreply.github.com> Date: Fri, 1 Sep 2023 14:13:36 +0100 Subject: [PATCH 05/15] contracts: updated consensus.Deposit test - it's expected to fail --- contracts/test/subcall.ts | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/contracts/test/subcall.ts b/contracts/test/subcall.ts index 0e785992..79a24c73 100644 --- a/contracts/test/subcall.ts +++ b/contracts/test/subcall.ts @@ -5,7 +5,7 @@ import * as cborg from 'cborg'; import { SubcallTests } from '../typechain-types/contracts/tests/SubcallTests'; import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/dist/src/signer-with-address"; import { parseEther } from 'ethers/lib/utils'; -import { BigNumber, BigNumberish, Signer } from 'ethers'; +import { BigNumber, BigNumberish, ContractReceipt, Signer } from 'ethers'; function fromBigInt(bi: BigNumberish) : Uint8Array { return ethers.utils.arrayify(ethers.utils.zeroPad(ethers.utils.hexlify(bi), 16)); @@ -24,6 +24,16 @@ async function ensureBalance(contract: SubcallTests, initialBalance:BigNumber, o expect(await contract.provider.getBalance(contract.address)).eq(initialBalance); } +function decodeResult(receipt:ContractReceipt){ + const event = receipt.events![0].args! as unknown as {status:number, data:string}; + return { + status: event.status, + data: event.status == 0 + ? cborg.decode(ethers.utils.arrayify(event.data)) + : new TextDecoder().decode(ethers.utils.arrayify(event.data)) + } +} + describe('Subcall', () => { let contract: SubcallTests; let owner: SignerWithAddress; @@ -133,25 +143,33 @@ describe('Subcall', () => { expect(await contract.provider.getBalance(contract.address)).eq(initialBalance); }); - /* - it('consensus.Deposit', async () => { + it('consensus.Withdraw', async () => { // Ensure contract has an initial balance const initialBalance = parseEther('1.0'); await ensureBalance(contract, initialBalance, owner); - let tx = await contract.testConsensusDeposit(kp.publicKey, 0); + expect(await contract.provider.getBalance(contract.address)).eq(initialBalance); + + let tx = await contract.testConsensusWithdraw(kp.publicKey, 0); await tx.wait(); expect(await contract.provider.getBalance(contract.address)).eq(initialBalance); }); - */ - it('consensus.Withdraw', async () => { + it('consensus.Deposit', async () => { // Ensure contract has an initial balance const initialBalance = parseEther('1.0'); await ensureBalance(contract, initialBalance, owner); - let tx = await contract.testConsensusWithdraw(kp.publicKey, 0); - await tx.wait(); - expect(await contract.provider.getBalance(contract.address)).eq(initialBalance); + const message = cborg.encode({ + to: kp.publicKey, + amount: [fromBigInt(0), new Uint8Array()] + }); + const tx = await contract.testSubcall('consensus.Deposit', message); + let result = decodeResult(await tx.wait()); + + // consensus.Deposit cannot be called from Solidity + // It requires the transaction signer to be a consensus account! + expect(result.status).eq(4); + expect(result.data).eq("consensus"); }); }); From 6073982b109766fa7cb28c5e60491c7da3b7f627 Mon Sep 17 00:00:00 2001 From: CedarMist <134699267+CedarMist@users.noreply.github.com> Date: Tue, 5 Sep 2023 12:18:15 +0100 Subject: [PATCH 06/15] contracts: ran formatter --- contracts/contracts/Subcall.sol | 185 ++++++++++++--------- contracts/contracts/tests/SubcallTests.sol | 45 ++--- contracts/test/subcall.ts | 94 +++++++---- 3 files changed, 184 insertions(+), 140 deletions(-) diff --git a/contracts/contracts/Subcall.sol b/contracts/contracts/Subcall.sol index 22a08f8e..a978e560 100644 --- a/contracts/contracts/Subcall.sol +++ b/contracts/contracts/Subcall.sol @@ -8,64 +8,75 @@ type StakingPublicKey is bytes21; type StakingSecretKey is bytes32; - library ConsensusUtils { - string constant private ADDRESS_V0_CONTEXT_IDENTIFIER = "oasis-core/address: staking"; - uint8 constant private ADDRESS_V0_CONTEXT_VERSION = 0; + string private constant ADDRESS_V0_CONTEXT_IDENTIFIER = + "oasis-core/address: staking"; + uint8 private constant ADDRESS_V0_CONTEXT_VERSION = 0; - string constant internal V0_SECP256K1ETH_CONTEXT_IDENTIFIER = "oasis-runtime-sdk/address: secp256k1eth"; - uint8 constant internal V0_SECP256K1ETH_CONTEXT_VERSION = 0; + string internal constant V0_SECP256K1ETH_CONTEXT_IDENTIFIER = + "oasis-runtime-sdk/address: secp256k1eth"; + uint8 internal constant V0_SECP256K1ETH_CONTEXT_VERSION = 0; function generateStakingAddress(bytes memory personalization) - internal view + internal + view returns (StakingPublicKey publicKey, StakingSecretKey secretKey) { bytes memory sk = Sapphire.randomBytes(32, personalization); - (bytes memory pk,) = Sapphire.generateSigningKeyPair( - Sapphire.SigningAlg.Ed25519Oasis, sk); + (bytes memory pk, ) = Sapphire.generateSigningKeyPair( + Sapphire.SigningAlg.Ed25519Oasis, + sk + ); - publicKey = StakingPublicKey.wrap(_stakingAddressFromPublicKey(bytes32(pk))); + publicKey = StakingPublicKey.wrap( + _stakingAddressFromPublicKey(bytes32(pk)) + ); secretKey = StakingSecretKey.wrap(bytes32(sk)); } function _stakingAddressFromPublicKey(bytes32 ed25519publicKey) - internal view + internal + view returns (bytes21) { - return _addressFromData( - ADDRESS_V0_CONTEXT_IDENTIFIER, - ADDRESS_V0_CONTEXT_VERSION, - abi.encodePacked(ed25519publicKey)); + return + _addressFromData( + ADDRESS_V0_CONTEXT_IDENTIFIER, + ADDRESS_V0_CONTEXT_VERSION, + abi.encodePacked(ed25519publicKey) + ); } function _addressFromData( string memory contextIdentifier, uint8 contextVersion, bytes memory data - ) - internal view - returns (bytes21) - { - return bytes21( - abi.encodePacked( - contextVersion, - bytes20( - sha512_256( - abi.encodePacked( - contextIdentifier, - contextVersion, - data))))); + ) internal view returns (bytes21) { + return + bytes21( + abi.encodePacked( + contextVersion, + bytes20( + sha512_256( + abi.encodePacked( + contextIdentifier, + contextVersion, + data + ) + ) + ) + ) + ); } } - library Subcall { address internal constant SUBCALL = 0x0100000000000000000000000000000000000103; - error Subcall_Error (); + error Subcall_Error(); /** * Submit a native message to the Oasis runtime layer @@ -85,32 +96,35 @@ library Subcall { abi.encode(method, body) ); - if( false == success ) { + if (false == success) { revert Subcall_Error(); } (status, data) = abi.decode(tmp, (uint64, bytes)); } - - function _subcall_to_amount ( + function _subcallWithToAndAmount( string memory method, StakingPublicKey to, uint128 value - ) - internal - returns (uint64 status, bytes memory data) - { + ) internal returns (uint64 status, bytes memory data) { (status, data) = subcall( method, abi.encodePacked( - hex"a262", "to", hex"55", to, - hex"66", "amount", hex"8250", value, hex"40" - )); + hex"a262", + "to", + hex"55", + to, + hex"66", + "amount", + hex"8250", + value, + hex"40" + ) + ); } - - error ConsensusUndelegate_Error(uint64 status, string data); + error ConsensusUndelegateError(uint64 status, string data); /** * Start the undelegation process of the given number of shares from @@ -119,26 +133,29 @@ library Subcall { * @param from Public key which shares were delegated to * @param shares Number of shares to withdraw back to us */ - function consensus_Undelegate( - StakingPublicKey from, - uint128 shares - ) + function consensusUndelegate(StakingPublicKey from, uint128 shares) internal { (uint64 status, bytes memory data) = subcall( "consensus.Undelegate", abi.encodePacked( - hex"a264", "from", hex"55", from, - hex"66", "shares", hex"50", shares - )); + hex"a264", + "from", + hex"55", + from, + hex"66", + "shares", + hex"50", + shares + ) + ); - if( status != 0 ) { - revert ConsensusUndelegate_Error(status, string(data)); + if (status != 0) { + revert ConsensusUndelegateError(status, string(data)); } } - - error ConsensusDelegate_Error(uint64 status, string data); + error ConsensusDelegateError(uint64 status, string data); /** * Delegate native token to consensus level @@ -146,18 +163,19 @@ library Subcall { * @param to Staking account * @param value native token amount (in wei) */ - function consensus_Delegate(StakingPublicKey to, uint128 value) - internal - { - (uint64 status, bytes memory data) = _subcall_to_amount("consensus.Delegate", to, value); + function consensusDelegate(StakingPublicKey to, uint128 value) internal { + (uint64 status, bytes memory data) = _subcallWithToAndAmount( + "consensus.Delegate", + to, + value + ); - if( status != 0 ) { - revert ConsensusDelegate_Error(status, string(data)); + if (status != 0) { + revert ConsensusDelegateError(status, string(data)); } } - - error ConsensusDeposit_Error(uint64 status, bytes data); + error ConsensusDepositError(uint64 status, bytes data); /** * Transfer from consensus staking account to an account in this runtime. @@ -168,18 +186,19 @@ library Subcall { * @param to runtime account which gets the tokens * @param value native token amount (in wei) */ - function consensus_Deposit(StakingPublicKey to, uint128 value) - internal - { - (uint64 status, bytes memory data) = _subcall_to_amount("consensus.Deposit", to, value); + function consensusDeposit(StakingPublicKey to, uint128 value) internal { + (uint64 status, bytes memory data) = _subcallWithToAndAmount( + "consensus.Deposit", + to, + value + ); - if( status != 0 ) { - revert ConsensusDeposit_Error(status, data); + if (status != 0) { + revert ConsensusDepositError(status, data); } } - - error ConsensusWithdraw_Error(uint64 status, string data); + error ConsensusWithdrawError(uint64 status, string data); /** * Transfer from an account in this runtime to a consensus staking account. @@ -187,18 +206,19 @@ library Subcall { * @param to consensus staking account which gets the tokens * @param value native token amount (in wei) */ - function consensus_Withdraw(StakingPublicKey to, uint128 value) - internal - { - (uint64 status, bytes memory data) = _subcall_to_amount("consensus.Withdraw", to, value); + function consensusWithdraw(StakingPublicKey to, uint128 value) internal { + (uint64 status, bytes memory data) = _subcallWithToAndAmount( + "consensus.Withdraw", + to, + value + ); - if( status != 0 ) { - revert ConsensusWithdraw_Error(status, string(data)); + if (status != 0) { + revert ConsensusWithdrawError(status, string(data)); } } - - error AccountsTransfer_Error(uint64 status, string data); + error AccountsTransferError(uint64 status, string data); /** * Perform a transfer to another account @@ -208,16 +228,15 @@ library Subcall { * @param to Destination account * @param value native token amount (in wei) */ - function accounts_Transfer(address to, uint128 value) - internal - { - (uint64 status, bytes memory data) = _subcall_to_amount( + function accountsTransfer(address to, uint128 value) internal { + (uint64 status, bytes memory data) = _subcallWithToAndAmount( "accounts.Transfer", StakingPublicKey.wrap(bytes21(abi.encodePacked(uint8(0x00), to))), - value); + value + ); - if( status != 0 ) { - revert AccountsTransfer_Error(status, string(data)); + if (status != 0) { + revert AccountsTransferError(status, string(data)); } } } diff --git a/contracts/contracts/tests/SubcallTests.sol b/contracts/contracts/tests/SubcallTests.sol index ee20e7a4..73956655 100644 --- a/contracts/contracts/tests/SubcallTests.sol +++ b/contracts/contracts/tests/SubcallTests.sol @@ -3,26 +3,19 @@ pragma solidity ^0.8.0; import {Sapphire} from "../Sapphire.sol"; -import {Subcall,ConsensusUtils,StakingPublicKey,StakingSecretKey} from "../Subcall.sol"; +import {Subcall, ConsensusUtils, StakingPublicKey, StakingSecretKey} from "../Subcall.sol"; contract SubcallTests { event SubcallResult(uint64 status, bytes data); - constructor () - payable - { - - } + constructor() payable {} - receive () external payable - { } + receive() external payable {} - function generateRandomAddress () - external view - returns ( - StakingPublicKey publicKey, - StakingSecretKey secretKey - ) + function generateRandomAddress() + external + view + returns (StakingPublicKey publicKey, StakingSecretKey secretKey) { return ConsensusUtils.generateStakingAddress(""); } @@ -35,33 +28,29 @@ contract SubcallTests { emit SubcallResult(status, data); } - function testAccountsTransfer (address to, uint128 value) - external - { - Subcall.accounts_Transfer(to, value); + function testAccountsTransfer(address to, uint128 value) external { + Subcall.accountsTransfer(to, value); } - function testConsensusDelegate (StakingPublicKey to, uint128 value) + function testConsensusDelegate(StakingPublicKey to, uint128 value) external { - Subcall.consensus_Delegate(to, value); + Subcall.consensusDelegate(to, value); } - function testConsensusUndelegate (StakingPublicKey to, uint128 value) + function tesaccountsTransferate(StakingPublicKey to, uint128 value) external { - Subcall.consensus_Undelegate(to, value); + Subcall.consensusUndelegate(to, value); } - function testConsensusDeposit (StakingPublicKey to, uint128 value) - external - { - Subcall.consensus_Deposit(to, value); + function testConsensusDeposit(StakingPublicKey to, uint128 value) external { + Subcall.consensusDeposit(to, value); } - function testConsensusWithdraw (StakingPublicKey to, uint128 value) + function testConsensusWithdraw(StakingPublicKey to, uint128 value) external { - Subcall.consensus_Withdraw(to, value); + Subcall.consensusWithdraw(to, value); } } diff --git a/contracts/test/subcall.ts b/contracts/test/subcall.ts index 79a24c73..74d28134 100644 --- a/contracts/test/subcall.ts +++ b/contracts/test/subcall.ts @@ -3,35 +3,47 @@ import { expect } from 'chai'; import * as oasis from '@oasisprotocol/client'; import * as cborg from 'cborg'; import { SubcallTests } from '../typechain-types/contracts/tests/SubcallTests'; -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/dist/src/signer-with-address"; +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/dist/src/signer-with-address'; import { parseEther } from 'ethers/lib/utils'; import { BigNumber, BigNumberish, ContractReceipt, Signer } from 'ethers'; -function fromBigInt(bi: BigNumberish) : Uint8Array { - return ethers.utils.arrayify(ethers.utils.zeroPad(ethers.utils.hexlify(bi), 16)); +function fromBigInt(bi: BigNumberish): Uint8Array { + return ethers.utils.arrayify( + ethers.utils.zeroPad(ethers.utils.hexlify(bi), 16), + ); } -async function ensureBalance(contract: SubcallTests, initialBalance:BigNumber, owner: SignerWithAddress) { +async function ensureBalance( + contract: SubcallTests, + initialBalance: BigNumber, + owner: SignerWithAddress, +) { const balance = await contract.provider.getBalance(contract.address); if (balance.lt(initialBalance)) { const resp = await owner.sendTransaction({ to: contract.address, value: initialBalance.sub(balance), - data: "0x" + data: '0x', }); await resp.wait(); } - expect(await contract.provider.getBalance(contract.address)).eq(initialBalance); + expect(await contract.provider.getBalance(contract.address)).eq( + initialBalance, + ); } -function decodeResult(receipt:ContractReceipt){ - const event = receipt.events![0].args! as unknown as {status:number, data:string}; +function decodeResult(receipt: ContractReceipt) { + const event = receipt.events![0].args! as unknown as { + status: number; + data: string; + }; return { status: event.status, - data: event.status == 0 - ? cborg.decode(ethers.utils.arrayify(event.data)) - : new TextDecoder().decode(ethers.utils.arrayify(event.data)) - } + data: + event.status == 0 + ? cborg.decode(ethers.utils.arrayify(event.data)) + : new TextDecoder().decode(ethers.utils.arrayify(event.data)), + }; } describe('Subcall', () => { @@ -39,24 +51,28 @@ describe('Subcall', () => { let owner: SignerWithAddress; let ownerAddr: string; let ownerNativeAddr: Uint8Array; - let kp: {publicKey: Uint8Array, secretKey: Uint8Array}; + let kp: { publicKey: Uint8Array; secretKey: Uint8Array }; before(async () => { const factory = await ethers.getContractFactory('SubcallTests'); - contract = (await factory.deploy({value: parseEther('1.0')})) as SubcallTests; + contract = (await factory.deploy({ + value: parseEther('1.0'), + })) as SubcallTests; const signers = await ethers.getSigners(); owner = signers[0]; ownerAddr = await owner.getAddress(); // Convert Ethereum address to native bytes with version prefix (V1=0x00) - ownerNativeAddr = ethers.utils.arrayify(ethers.utils.zeroPad(ownerAddr, 21)); + ownerNativeAddr = ethers.utils.arrayify( + ethers.utils.zeroPad(ownerAddr, 21), + ); expect(ownerNativeAddr.length).eq(21); const rawKp = await contract.generateRandomAddress(); kp = { publicKey: ethers.utils.arrayify(rawKp.publicKey), - secretKey: ethers.utils.arrayify(rawKp.secretKey) + secretKey: ethers.utils.arrayify(rawKp.secretKey), }; }); @@ -65,9 +81,12 @@ describe('Subcall', () => { // Verify @oasisprotocol/client matches Solidity const alice = oasis.signature.NaclSigner.fromSeed( - ethers.utils.arrayify(newKeypair.secretKey), 'this key is not important', + ethers.utils.arrayify(newKeypair.secretKey), + 'this key is not important', + ); + const computedPublicKey = ethers.utils.hexlify( + await oasis.staking.addressFromPublicKey(alice.public()), ); - const computedPublicKey = ethers.utils.hexlify(await oasis.staking.addressFromPublicKey(alice.public())); expect(computedPublicKey).eq(ethers.utils.hexlify(newKeypair.publicKey)); }); @@ -81,13 +100,16 @@ describe('Subcall', () => { const balance = await contract.provider.getBalance(contract.address); const message = cborg.encode({ to: ownerNativeAddr, - amount: [fromBigInt(balance.sub(1)), new Uint8Array()] + amount: [fromBigInt(balance.sub(1)), new Uint8Array()], }); let tx = await contract.testSubcall('accounts.Transfer', message); let receipt = await tx.wait(); // Transfer is success with: status=0, data=null - const event = receipt.events![0].args! as unknown as {status:number, data:string}; + const event = receipt.events![0].args! as unknown as { + status: number; + data: string; + }; expect(event.status).eq(0); expect(cborg.decode(ethers.utils.arrayify(event.data))).is.null; @@ -110,21 +132,29 @@ describe('Subcall', () => { // Delegate 0, ensure balance does not change let tx = await contract.testConsensusDelegate(kp.publicKey, 0); await tx.wait(); - expect(await contract.provider.getBalance(contract.address)).eq(initialBalance); + expect(await contract.provider.getBalance(contract.address)).eq( + initialBalance, + ); // Manually encode & submit consensus.Delegate message const message = cborg.encode({ to: kp.publicKey, - amount: [fromBigInt(0), new Uint8Array()] + amount: [fromBigInt(0), new Uint8Array()], }); tx = await contract.testSubcall('consensus.Delegate', message); let receipt = await tx.wait(); // Transfer is success with: status=0, data=null - const event = receipt.events![0].args! as unknown as {status:number, data:string}; + const event = receipt.events![0].args! as unknown as { + status: number; + data: string; + }; const decodedEvent = { status: event.status, - data: event.status == 0 ? cborg.decode(ethers.utils.arrayify(event.data)) : new TextDecoder().decode(ethers.utils.arrayify(event.data)) + data: + event.status == 0 + ? cborg.decode(ethers.utils.arrayify(event.data)) + : new TextDecoder().decode(ethers.utils.arrayify(event.data)), }; expect(event.status).eq(0); expect(cborg.decode(ethers.utils.arrayify(event.data))).is.null; @@ -140,7 +170,9 @@ describe('Subcall', () => { let tx = await contract.testConsensusUndelegate(kp.publicKey, 0); await tx.wait(); - expect(await contract.provider.getBalance(contract.address)).eq(initialBalance); + expect(await contract.provider.getBalance(contract.address)).eq( + initialBalance, + ); }); it('consensus.Withdraw', async () => { @@ -148,11 +180,15 @@ describe('Subcall', () => { const initialBalance = parseEther('1.0'); await ensureBalance(contract, initialBalance, owner); - expect(await contract.provider.getBalance(contract.address)).eq(initialBalance); + expect(await contract.provider.getBalance(contract.address)).eq( + initialBalance, + ); let tx = await contract.testConsensusWithdraw(kp.publicKey, 0); await tx.wait(); - expect(await contract.provider.getBalance(contract.address)).eq(initialBalance); + expect(await contract.provider.getBalance(contract.address)).eq( + initialBalance, + ); }); it('consensus.Deposit', async () => { @@ -162,7 +198,7 @@ describe('Subcall', () => { const message = cborg.encode({ to: kp.publicKey, - amount: [fromBigInt(0), new Uint8Array()] + amount: [fromBigInt(0), new Uint8Array()], }); const tx = await contract.testSubcall('consensus.Deposit', message); let result = decodeResult(await tx.wait()); @@ -170,6 +206,6 @@ describe('Subcall', () => { // consensus.Deposit cannot be called from Solidity // It requires the transaction signer to be a consensus account! expect(result.status).eq(4); - expect(result.data).eq("consensus"); + expect(result.data).eq('consensus'); }); }); From fa6037bcf8a41aca45b99183651041d7bc3e95b3 Mon Sep 17 00:00:00 2001 From: CedarMist <134699267+CedarMist@users.noreply.github.com> Date: Tue, 5 Sep 2023 12:29:47 +0100 Subject: [PATCH 07/15] contracts: fixed variable name --- contracts/contracts/tests/SubcallTests.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/contracts/tests/SubcallTests.sol b/contracts/contracts/tests/SubcallTests.sol index 73956655..39d6737c 100644 --- a/contracts/contracts/tests/SubcallTests.sol +++ b/contracts/contracts/tests/SubcallTests.sol @@ -38,7 +38,7 @@ contract SubcallTests { Subcall.consensusDelegate(to, value); } - function tesaccountsTransferate(StakingPublicKey to, uint128 value) + function testConsensusUndelegate(StakingPublicKey to, uint128 value) external { Subcall.consensusUndelegate(to, value); From bdf8f42f0c4bfaf2f6840bfc2b738dba2b27ef23 Mon Sep 17 00:00:00 2001 From: CedarMist <134699267+CedarMist@users.noreply.github.com> Date: Tue, 5 Sep 2023 12:42:40 +0100 Subject: [PATCH 08/15] contracts: removed consensus.Deposit --- contracts/contracts/Subcall.sol | 23 ---------------------- contracts/contracts/tests/SubcallTests.sol | 4 ---- contracts/test/subcall.ts | 18 ----------------- 3 files changed, 45 deletions(-) diff --git a/contracts/contracts/Subcall.sol b/contracts/contracts/Subcall.sol index a978e560..526c6543 100644 --- a/contracts/contracts/Subcall.sol +++ b/contracts/contracts/Subcall.sol @@ -175,29 +175,6 @@ library Subcall { } } - error ConsensusDepositError(uint64 status, bytes data); - - /** - * Transfer from consensus staking account to an account in this runtime. - * - * The transaction signer has a consensus layer allowance benefiting this - * runtime's staking address. - * - * @param to runtime account which gets the tokens - * @param value native token amount (in wei) - */ - function consensusDeposit(StakingPublicKey to, uint128 value) internal { - (uint64 status, bytes memory data) = _subcallWithToAndAmount( - "consensus.Deposit", - to, - value - ); - - if (status != 0) { - revert ConsensusDepositError(status, data); - } - } - error ConsensusWithdrawError(uint64 status, string data); /** diff --git a/contracts/contracts/tests/SubcallTests.sol b/contracts/contracts/tests/SubcallTests.sol index 39d6737c..6f190cdb 100644 --- a/contracts/contracts/tests/SubcallTests.sol +++ b/contracts/contracts/tests/SubcallTests.sol @@ -44,10 +44,6 @@ contract SubcallTests { Subcall.consensusUndelegate(to, value); } - function testConsensusDeposit(StakingPublicKey to, uint128 value) external { - Subcall.consensusDeposit(to, value); - } - function testConsensusWithdraw(StakingPublicKey to, uint128 value) external { diff --git a/contracts/test/subcall.ts b/contracts/test/subcall.ts index 74d28134..fa7d85d0 100644 --- a/contracts/test/subcall.ts +++ b/contracts/test/subcall.ts @@ -190,22 +190,4 @@ describe('Subcall', () => { initialBalance, ); }); - - it('consensus.Deposit', async () => { - // Ensure contract has an initial balance - const initialBalance = parseEther('1.0'); - await ensureBalance(contract, initialBalance, owner); - - const message = cborg.encode({ - to: kp.publicKey, - amount: [fromBigInt(0), new Uint8Array()], - }); - const tx = await contract.testSubcall('consensus.Deposit', message); - let result = decodeResult(await tx.wait()); - - // consensus.Deposit cannot be called from Solidity - // It requires the transaction signer to be a consensus account! - expect(result.status).eq(4); - expect(result.data).eq('consensus'); - }); }); From 0472e8ee73b61d8d090a2d211f084c1af487b4a7 Mon Sep 17 00:00:00 2001 From: CedarMist <134699267+CedarMist@users.noreply.github.com> Date: Tue, 5 Sep 2023 14:10:06 +0100 Subject: [PATCH 09/15] contracts: use constants, made split of ConsensusUtils & Subcall & Sapphire more well defined --- contracts/contracts/ConsensusUtils.sol | 69 ++++++++++++++++++ contracts/contracts/Sapphire.sol | 25 ------- contracts/contracts/Subcall.sol | 85 +++------------------- contracts/contracts/tests/SubcallTests.sol | 6 +- contracts/test/subcall.ts | 31 ++------ 5 files changed, 91 insertions(+), 125 deletions(-) create mode 100644 contracts/contracts/ConsensusUtils.sol diff --git a/contracts/contracts/ConsensusUtils.sol b/contracts/contracts/ConsensusUtils.sol new file mode 100644 index 00000000..5f58962c --- /dev/null +++ b/contracts/contracts/ConsensusUtils.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: Apache-2.0 + +pragma solidity ^0.8.0; + +import {sha512_256, Sapphire} from "./Sapphire.sol"; + +type StakingPublicKey is bytes21; + +type StakingSecretKey is bytes32; + +library ConsensusUtils { + string private constant ADDRESS_V0_CONTEXT_IDENTIFIER = + "oasis-core/address: staking"; + uint8 private constant ADDRESS_V0_CONTEXT_VERSION = 0; + + function generateStakingAddress(bytes memory personalization) + internal + view + returns (StakingPublicKey publicKey, StakingSecretKey secretKey) + { + bytes memory sk = Sapphire.randomBytes(32, personalization); + + (bytes memory pk, ) = Sapphire.generateSigningKeyPair( + Sapphire.SigningAlg.Ed25519Oasis, + sk + ); + + publicKey = StakingPublicKey.wrap( + _stakingAddressFromPublicKey(bytes32(pk)) + ); + + secretKey = StakingSecretKey.wrap(bytes32(sk)); + } + + function _stakingAddressFromPublicKey(bytes32 ed25519publicKey) + internal + view + returns (bytes21) + { + return + _addressFromData( + ADDRESS_V0_CONTEXT_IDENTIFIER, + ADDRESS_V0_CONTEXT_VERSION, + abi.encodePacked(ed25519publicKey) + ); + } + + function _addressFromData( + string memory contextIdentifier, + uint8 contextVersion, + bytes memory data + ) internal view returns (bytes21) { + return + bytes21( + abi.encodePacked( + contextVersion, + bytes20( + sha512_256( + abi.encodePacked( + contextIdentifier, + contextVersion, + data + ) + ) + ) + ) + ); + } +} diff --git a/contracts/contracts/Sapphire.sol b/contracts/contracts/Sapphire.sol index bb2bb9d6..1fd02b5d 100644 --- a/contracts/contracts/Sapphire.sol +++ b/contracts/contracts/Sapphire.sol @@ -29,8 +29,6 @@ library Sapphire { 0x0100000000000000000000000000000000000101; address internal constant SHA512 = 0x0100000000000000000000000000000000000102; - address internal constant SUBCALL = - 0x0100000000000000000000000000000000000103; type Curve25519PublicKey is bytes32; type Curve25519SecretKey is bytes32; @@ -223,29 +221,6 @@ library Sapphire { require(success, "verify: failed"); return abi.decode(v, (bool)); } - - /** - * Submit a native message to the Oasis runtime layer - * - * Messages which re-enter the EVM module are forbidden: evm.* - * - * @param method Native message type - * @param body CBOR encoded body - * @return status_code Result of call - * @return data CBOR encoded result - */ - function subcall(string memory method, bytes memory body) - internal - returns (uint64 status_code, bytes memory data) - { - (bool success, bytes memory tmp) = SUBCALL.call( - abi.encode(method, body) - ); - - require(success, "subcall"); - - (status_code, data) = abi.decode(tmp, (uint64, bytes)); - } } /** diff --git a/contracts/contracts/Subcall.sol b/contracts/contracts/Subcall.sol index 526c6543..e9506e4c 100644 --- a/contracts/contracts/Subcall.sol +++ b/contracts/contracts/Subcall.sol @@ -2,77 +2,14 @@ pragma solidity ^0.8.0; -import {sha512_256, Sapphire} from "./Sapphire.sol"; - -type StakingPublicKey is bytes21; - -type StakingSecretKey is bytes32; - -library ConsensusUtils { - string private constant ADDRESS_V0_CONTEXT_IDENTIFIER = - "oasis-core/address: staking"; - uint8 private constant ADDRESS_V0_CONTEXT_VERSION = 0; - - string internal constant V0_SECP256K1ETH_CONTEXT_IDENTIFIER = - "oasis-runtime-sdk/address: secp256k1eth"; - uint8 internal constant V0_SECP256K1ETH_CONTEXT_VERSION = 0; - - function generateStakingAddress(bytes memory personalization) - internal - view - returns (StakingPublicKey publicKey, StakingSecretKey secretKey) - { - bytes memory sk = Sapphire.randomBytes(32, personalization); - - (bytes memory pk, ) = Sapphire.generateSigningKeyPair( - Sapphire.SigningAlg.Ed25519Oasis, - sk - ); - - publicKey = StakingPublicKey.wrap( - _stakingAddressFromPublicKey(bytes32(pk)) - ); - - secretKey = StakingSecretKey.wrap(bytes32(sk)); - } - - function _stakingAddressFromPublicKey(bytes32 ed25519publicKey) - internal - view - returns (bytes21) - { - return - _addressFromData( - ADDRESS_V0_CONTEXT_IDENTIFIER, - ADDRESS_V0_CONTEXT_VERSION, - abi.encodePacked(ed25519publicKey) - ); - } - - function _addressFromData( - string memory contextIdentifier, - uint8 contextVersion, - bytes memory data - ) internal view returns (bytes21) { - return - bytes21( - abi.encodePacked( - contextVersion, - bytes20( - sha512_256( - abi.encodePacked( - contextIdentifier, - contextVersion, - data - ) - ) - ) - ) - ); - } -} +import {StakingPublicKey,StakingSecretKey} from "./ConsensusUtils.sol"; library Subcall { + string private constant CONSENSUS_DELEGATE = "consensus.Delegate"; + string private constant CONSENSUS_UNDELEGATE = "consensus.Undelegate"; + string private constant CONSENSUS_WITHDRAW = "consensus.Withdraw"; + string private constant ACCOUNTS_TRANSFER = "accounts.Transfer"; + address internal constant SUBCALL = 0x0100000000000000000000000000000000000103; @@ -96,7 +33,7 @@ library Subcall { abi.encode(method, body) ); - if (false == success) { + if (!success) { revert Subcall_Error(); } @@ -137,7 +74,7 @@ library Subcall { internal { (uint64 status, bytes memory data) = subcall( - "consensus.Undelegate", + CONSENSUS_UNDELEGATE, abi.encodePacked( hex"a264", "from", @@ -165,7 +102,7 @@ library Subcall { */ function consensusDelegate(StakingPublicKey to, uint128 value) internal { (uint64 status, bytes memory data) = _subcallWithToAndAmount( - "consensus.Delegate", + CONSENSUS_DELEGATE, to, value ); @@ -185,7 +122,7 @@ library Subcall { */ function consensusWithdraw(StakingPublicKey to, uint128 value) internal { (uint64 status, bytes memory data) = _subcallWithToAndAmount( - "consensus.Withdraw", + CONSENSUS_WITHDRAW, to, value ); @@ -207,7 +144,7 @@ library Subcall { */ function accountsTransfer(address to, uint128 value) internal { (uint64 status, bytes memory data) = _subcallWithToAndAmount( - "accounts.Transfer", + ACCOUNTS_TRANSFER, StakingPublicKey.wrap(bytes21(abi.encodePacked(uint8(0x00), to))), value ); diff --git a/contracts/contracts/tests/SubcallTests.sol b/contracts/contracts/tests/SubcallTests.sol index 6f190cdb..91e2ef3c 100644 --- a/contracts/contracts/tests/SubcallTests.sol +++ b/contracts/contracts/tests/SubcallTests.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.0; -import {Sapphire} from "../Sapphire.sol"; -import {Subcall, ConsensusUtils, StakingPublicKey, StakingSecretKey} from "../Subcall.sol"; +import {ConsensusUtils, StakingPublicKey, StakingSecretKey} from "../ConsensusUtils.sol"; +import {Subcall} from "../Subcall.sol"; contract SubcallTests { event SubcallResult(uint64 status, bytes data); @@ -23,7 +23,7 @@ contract SubcallTests { function testSubcall(string memory method, bytes memory data) external { uint64 status; - (status, data) = Sapphire.subcall(method, data); + (status, data) = Subcall.subcall(method, data); emit SubcallResult(status, data); } diff --git a/contracts/test/subcall.ts b/contracts/test/subcall.ts index fa7d85d0..0ec22ef0 100644 --- a/contracts/test/subcall.ts +++ b/contracts/test/subcall.ts @@ -98,20 +98,16 @@ describe('Subcall', () => { // transfer balance-1 back to owner, then wait for transaction to be mined const balance = await contract.provider.getBalance(contract.address); - const message = cborg.encode({ + let tx = await contract.testSubcall('accounts.Transfer', cborg.encode({ to: ownerNativeAddr, amount: [fromBigInt(balance.sub(1)), new Uint8Array()], - }); - let tx = await contract.testSubcall('accounts.Transfer', message); + })); let receipt = await tx.wait(); // Transfer is success with: status=0, data=null - const event = receipt.events![0].args! as unknown as { - status: number; - data: string; - }; + const event = decodeResult(receipt); expect(event.status).eq(0); - expect(cborg.decode(ethers.utils.arrayify(event.data))).is.null; + expect(event.data).is.null; // Ensure contract only has 1 wei left expect(await contract.provider.getBalance(contract.address)).eq(1); @@ -137,27 +133,16 @@ describe('Subcall', () => { ); // Manually encode & submit consensus.Delegate message - const message = cborg.encode({ + tx = await contract.testSubcall('consensus.Delegate', cborg.encode({ to: kp.publicKey, amount: [fromBigInt(0), new Uint8Array()], - }); - tx = await contract.testSubcall('consensus.Delegate', message); + })); let receipt = await tx.wait(); // Transfer is success with: status=0, data=null - const event = receipt.events![0].args! as unknown as { - status: number; - data: string; - }; - const decodedEvent = { - status: event.status, - data: - event.status == 0 - ? cborg.decode(ethers.utils.arrayify(event.data)) - : new TextDecoder().decode(ethers.utils.arrayify(event.data)), - }; + const event = decodeResult(receipt); expect(event.status).eq(0); - expect(cborg.decode(ethers.utils.arrayify(event.data))).is.null; + expect(event.data).is.null; // Ensure contract only no wei left //expect(await contract.provider.getBalance(contract.address)).eq(0); From 0475196dfe95eaba42588cd271169d48ff034661 Mon Sep 17 00:00:00 2001 From: CedarMist <134699267+CedarMist@users.noreply.github.com> Date: Tue, 5 Sep 2023 14:42:27 +0100 Subject: [PATCH 10/15] contracts: formatting --- contracts/contracts/Subcall.sol | 2 +- contracts/test/subcall.ts | 22 ++++++++++++++-------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/contracts/contracts/Subcall.sol b/contracts/contracts/Subcall.sol index e9506e4c..78bc13a3 100644 --- a/contracts/contracts/Subcall.sol +++ b/contracts/contracts/Subcall.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; -import {StakingPublicKey,StakingSecretKey} from "./ConsensusUtils.sol"; +import {StakingPublicKey, StakingSecretKey} from "./ConsensusUtils.sol"; library Subcall { string private constant CONSENSUS_DELEGATE = "consensus.Delegate"; diff --git a/contracts/test/subcall.ts b/contracts/test/subcall.ts index 0ec22ef0..283afd34 100644 --- a/contracts/test/subcall.ts +++ b/contracts/test/subcall.ts @@ -98,10 +98,13 @@ describe('Subcall', () => { // transfer balance-1 back to owner, then wait for transaction to be mined const balance = await contract.provider.getBalance(contract.address); - let tx = await contract.testSubcall('accounts.Transfer', cborg.encode({ - to: ownerNativeAddr, - amount: [fromBigInt(balance.sub(1)), new Uint8Array()], - })); + let tx = await contract.testSubcall( + 'accounts.Transfer', + cborg.encode({ + to: ownerNativeAddr, + amount: [fromBigInt(balance.sub(1)), new Uint8Array()], + }), + ); let receipt = await tx.wait(); // Transfer is success with: status=0, data=null @@ -133,10 +136,13 @@ describe('Subcall', () => { ); // Manually encode & submit consensus.Delegate message - tx = await contract.testSubcall('consensus.Delegate', cborg.encode({ - to: kp.publicKey, - amount: [fromBigInt(0), new Uint8Array()], - })); + tx = await contract.testSubcall( + 'consensus.Delegate', + cborg.encode({ + to: kp.publicKey, + amount: [fromBigInt(0), new Uint8Array()], + }), + ); let receipt = await tx.wait(); // Transfer is success with: status=0, data=null From 6cddc28c145e066866db269e516420a264bbc267 Mon Sep 17 00:00:00 2001 From: CedarMist <134699267+CedarMist@users.noreply.github.com> Date: Tue, 5 Sep 2023 16:33:22 +0100 Subject: [PATCH 11/15] contracts: corrected StakingPublicKey to StakingAddress --- contracts/contracts/ConsensusUtils.sol | 6 +++--- contracts/contracts/Subcall.sol | 14 ++++++-------- contracts/contracts/tests/SubcallTests.sol | 14 +++++--------- 3 files changed, 14 insertions(+), 20 deletions(-) diff --git a/contracts/contracts/ConsensusUtils.sol b/contracts/contracts/ConsensusUtils.sol index 5f58962c..e863561e 100644 --- a/contracts/contracts/ConsensusUtils.sol +++ b/contracts/contracts/ConsensusUtils.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.0; import {sha512_256, Sapphire} from "./Sapphire.sol"; -type StakingPublicKey is bytes21; +type StakingAddress is bytes21; type StakingSecretKey is bytes32; @@ -16,7 +16,7 @@ library ConsensusUtils { function generateStakingAddress(bytes memory personalization) internal view - returns (StakingPublicKey publicKey, StakingSecretKey secretKey) + returns (StakingAddress publicAddress, StakingSecretKey secretKey) { bytes memory sk = Sapphire.randomBytes(32, personalization); @@ -25,7 +25,7 @@ library ConsensusUtils { sk ); - publicKey = StakingPublicKey.wrap( + publicAddress = StakingAddress.wrap( _stakingAddressFromPublicKey(bytes32(pk)) ); diff --git a/contracts/contracts/Subcall.sol b/contracts/contracts/Subcall.sol index 78bc13a3..6d05f2b9 100644 --- a/contracts/contracts/Subcall.sol +++ b/contracts/contracts/Subcall.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; -import {StakingPublicKey, StakingSecretKey} from "./ConsensusUtils.sol"; +import {StakingAddress, StakingSecretKey} from "./ConsensusUtils.sol"; library Subcall { string private constant CONSENSUS_DELEGATE = "consensus.Delegate"; @@ -42,7 +42,7 @@ library Subcall { function _subcallWithToAndAmount( string memory method, - StakingPublicKey to, + StakingAddress to, uint128 value ) internal returns (uint64 status, bytes memory data) { (status, data) = subcall( @@ -70,9 +70,7 @@ library Subcall { * @param from Public key which shares were delegated to * @param shares Number of shares to withdraw back to us */ - function consensusUndelegate(StakingPublicKey from, uint128 shares) - internal - { + function consensusUndelegate(StakingAddress from, uint128 shares) internal { (uint64 status, bytes memory data) = subcall( CONSENSUS_UNDELEGATE, abi.encodePacked( @@ -100,7 +98,7 @@ library Subcall { * @param to Staking account * @param value native token amount (in wei) */ - function consensusDelegate(StakingPublicKey to, uint128 value) internal { + function consensusDelegate(StakingAddress to, uint128 value) internal { (uint64 status, bytes memory data) = _subcallWithToAndAmount( CONSENSUS_DELEGATE, to, @@ -120,7 +118,7 @@ library Subcall { * @param to consensus staking account which gets the tokens * @param value native token amount (in wei) */ - function consensusWithdraw(StakingPublicKey to, uint128 value) internal { + function consensusWithdraw(StakingAddress to, uint128 value) internal { (uint64 status, bytes memory data) = _subcallWithToAndAmount( CONSENSUS_WITHDRAW, to, @@ -145,7 +143,7 @@ library Subcall { function accountsTransfer(address to, uint128 value) internal { (uint64 status, bytes memory data) = _subcallWithToAndAmount( ACCOUNTS_TRANSFER, - StakingPublicKey.wrap(bytes21(abi.encodePacked(uint8(0x00), to))), + StakingAddress.wrap(bytes21(abi.encodePacked(uint8(0x00), to))), value ); diff --git a/contracts/contracts/tests/SubcallTests.sol b/contracts/contracts/tests/SubcallTests.sol index 91e2ef3c..1d537380 100644 --- a/contracts/contracts/tests/SubcallTests.sol +++ b/contracts/contracts/tests/SubcallTests.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; -import {ConsensusUtils, StakingPublicKey, StakingSecretKey} from "../ConsensusUtils.sol"; +import {ConsensusUtils, StakingAddress, StakingSecretKey} from "../ConsensusUtils.sol"; import {Subcall} from "../Subcall.sol"; contract SubcallTests { @@ -15,7 +15,7 @@ contract SubcallTests { function generateRandomAddress() external view - returns (StakingPublicKey publicKey, StakingSecretKey secretKey) + returns (StakingAddress publicKey, StakingSecretKey secretKey) { return ConsensusUtils.generateStakingAddress(""); } @@ -32,21 +32,17 @@ contract SubcallTests { Subcall.accountsTransfer(to, value); } - function testConsensusDelegate(StakingPublicKey to, uint128 value) - external - { + function testConsensusDelegate(StakingAddress to, uint128 value) external { Subcall.consensusDelegate(to, value); } - function testConsensusUndelegate(StakingPublicKey to, uint128 value) + function testConsensusUndelegate(StakingAddress to, uint128 value) external { Subcall.consensusUndelegate(to, value); } - function testConsensusWithdraw(StakingPublicKey to, uint128 value) - external - { + function testConsensusWithdraw(StakingAddress to, uint128 value) external { Subcall.consensusWithdraw(to, value); } } From 3d9ce3eb9799e4335a9607b92a0c9433dddaca40 Mon Sep 17 00:00:00 2001 From: CedarMist <134699267+CedarMist@users.noreply.github.com> Date: Tue, 5 Sep 2023 17:02:12 +0100 Subject: [PATCH 12/15] contracts: Apply documentation suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matevž Jekovec --- contracts/contracts/Subcall.sol | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/contracts/contracts/Subcall.sol b/contracts/contracts/Subcall.sol index 6d05f2b9..49db4353 100644 --- a/contracts/contracts/Subcall.sol +++ b/contracts/contracts/Subcall.sol @@ -67,7 +67,7 @@ library Subcall { * Start the undelegation process of the given number of shares from * consensus staking account to runtime account. * - * @param from Public key which shares were delegated to + * @param from Consensus address which shares were delegated to * @param shares Number of shares to withdraw back to us */ function consensusUndelegate(StakingAddress from, uint128 shares) internal { @@ -95,8 +95,8 @@ library Subcall { /** * Delegate native token to consensus level * - * @param to Staking account - * @param value native token amount (in wei) + * @param to Consensus address shares are delegated to + * @param value Native token amount (in wei) */ function consensusDelegate(StakingAddress to, uint128 value) internal { (uint64 status, bytes memory data) = _subcallWithToAndAmount( @@ -115,8 +115,8 @@ library Subcall { /** * Transfer from an account in this runtime to a consensus staking account. * - * @param to consensus staking account which gets the tokens - * @param value native token amount (in wei) + * @param to Consensus address which gets the tokens + * @param value Native token amount (in wei) */ function consensusWithdraw(StakingAddress to, uint128 value) internal { (uint64 status, bytes memory data) = _subcallWithToAndAmount( From c80331df19b2397964741ec8f408163f2c4c7134 Mon Sep 17 00:00:00 2001 From: CedarMist <134699267+CedarMist@users.noreply.github.com> Date: Sun, 17 Sep 2023 12:10:26 +0200 Subject: [PATCH 13/15] contracts: added more documentation to subcall & consensus utils --- contracts/contracts/ConsensusUtils.sol | 24 ++++++++++++++++++++++++ contracts/contracts/Subcall.sol | 5 +++++ 2 files changed, 29 insertions(+) diff --git a/contracts/contracts/ConsensusUtils.sol b/contracts/contracts/ConsensusUtils.sol index e863561e..058a3e2c 100644 --- a/contracts/contracts/ConsensusUtils.sol +++ b/contracts/contracts/ConsensusUtils.sol @@ -4,15 +4,29 @@ pragma solidity ^0.8.0; import {sha512_256, Sapphire} from "./Sapphire.sol"; +// 21 byte version-prefixed address (1 byte version, 20 bytes truncated digest ) type StakingAddress is bytes21; +/// 32 byte secret key type StakingSecretKey is bytes32; +/** + * @title Consensus-level utilities + * @dev Generate Oasis wallets for use with staking at the consensus level + */ library ConsensusUtils { + /// The unique context for v0 staking account addresses. + /// https://github.com/oasisprotocol/oasis-core/blob/master/go/staking/api/address.go#L16 string private constant ADDRESS_V0_CONTEXT_IDENTIFIER = "oasis-core/address: staking"; uint8 private constant ADDRESS_V0_CONTEXT_VERSION = 0; + /** + * @dev Generate a random Ed25519 wallet for Oasis consensus-layer staking + * @param personalization Optional user-specified entropy + * @return publicAddress Public address of the keypair + * @return secretKey Secret key for the keypair + */ function generateStakingAddress(bytes memory personalization) internal view @@ -32,6 +46,10 @@ library ConsensusUtils { secretKey = StakingSecretKey.wrap(bytes32(sk)); } + /** + * @dev Derive the staking address from the public key + * @param ed25519publicKey Ed25519 public key + */ function _stakingAddressFromPublicKey(bytes32 ed25519publicKey) internal view @@ -45,6 +63,12 @@ library ConsensusUtils { ); } + /** + * @dev Derive an Oasis-style address + * @param contextIdentifier Domain separator + * @param contextVersion Domain version + * @param data Public point of the keypair + */ function _addressFromData( string memory contextIdentifier, uint8 contextVersion, diff --git a/contracts/contracts/Subcall.sol b/contracts/contracts/Subcall.sol index 49db4353..f3b6ac49 100644 --- a/contracts/contracts/Subcall.sol +++ b/contracts/contracts/Subcall.sol @@ -4,6 +4,10 @@ pragma solidity ^0.8.0; import {StakingAddress, StakingSecretKey} from "./ConsensusUtils.sol"; +/** + * @title SDK Subcall wrappers + * @dev Interact with Oasis Runtime SDK modules from Sapphire + */ library Subcall { string private constant CONSENSUS_DELEGATE = "consensus.Delegate"; string private constant CONSENSUS_UNDELEGATE = "consensus.Undelegate"; @@ -13,6 +17,7 @@ library Subcall { address internal constant SUBCALL = 0x0100000000000000000000000000000000000103; + /// Only raised if the underlying subcall precompile does not succeed error Subcall_Error(); /** From e881d932fc06070db33c1a50005e489e12662b87 Mon Sep 17 00:00:00 2001 From: CedarMist <134699267+CedarMist@users.noreply.github.com> Date: Sun, 17 Sep 2023 12:15:54 +0200 Subject: [PATCH 14/15] contracts: more subcall documentation --- contracts/contracts/Subcall.sol | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/contracts/contracts/Subcall.sol b/contracts/contracts/Subcall.sol index f3b6ac49..20d1380a 100644 --- a/contracts/contracts/Subcall.sol +++ b/contracts/contracts/Subcall.sol @@ -14,6 +14,7 @@ library Subcall { string private constant CONSENSUS_WITHDRAW = "consensus.Withdraw"; string private constant ACCOUNTS_TRANSFER = "accounts.Transfer"; + /// Address of the SUBCALL precompile address internal constant SUBCALL = 0x0100000000000000000000000000000000000103; @@ -45,6 +46,14 @@ library Subcall { (status, data) = abi.decode(tmp, (uint64, bytes)); } + /** + * @dev Generic method to call `{to:address, amount:uint128}` + * @param method Runtime SDK method name ('module.Action') + * @param to Destination address + * @param value Amount specified + * @return status Non-zero on error + * @return data Module name on error + */ function _subcallWithToAndAmount( string memory method, StakingAddress to, From b49404e4e88492ae1e694e160e37fbd5600ff6e1 Mon Sep 17 00:00:00 2001 From: CedarMist <134699267+CedarMist@users.noreply.github.com> Date: Mon, 18 Sep 2023 15:19:22 +0100 Subject: [PATCH 15/15] contracts: minor documentation cleanups via matevz --- contracts/contracts/ConsensusUtils.sol | 4 ++-- contracts/contracts/Subcall.sol | 30 +++++++++++++------------- contracts/test/subcall.ts | 25 ++++++++++----------- 3 files changed, 28 insertions(+), 31 deletions(-) diff --git a/contracts/contracts/ConsensusUtils.sol b/contracts/contracts/ConsensusUtils.sol index 058a3e2c..c84d7e46 100644 --- a/contracts/contracts/ConsensusUtils.sol +++ b/contracts/contracts/ConsensusUtils.sol @@ -4,10 +4,10 @@ pragma solidity ^0.8.0; import {sha512_256, Sapphire} from "./Sapphire.sol"; -// 21 byte version-prefixed address (1 byte version, 20 bytes truncated digest ) +/// 21 byte version-prefixed address (1 byte version, 20 bytes truncated digest). type StakingAddress is bytes21; -/// 32 byte secret key +/// 32 byte secret key. type StakingSecretKey is bytes32; /** diff --git a/contracts/contracts/Subcall.sol b/contracts/contracts/Subcall.sol index 20d1380a..aa89593d 100644 --- a/contracts/contracts/Subcall.sol +++ b/contracts/contracts/Subcall.sol @@ -6,7 +6,7 @@ import {StakingAddress, StakingSecretKey} from "./ConsensusUtils.sol"; /** * @title SDK Subcall wrappers - * @dev Interact with Oasis Runtime SDK modules from Sapphire + * @dev Interact with Oasis Runtime SDK modules from Sapphire. */ library Subcall { string private constant CONSENSUS_DELEGATE = "consensus.Delegate"; @@ -18,11 +18,19 @@ library Subcall { address internal constant SUBCALL = 0x0100000000000000000000000000000000000103; - /// Only raised if the underlying subcall precompile does not succeed - error Subcall_Error(); + /// Raised if the underlying subcall precompile does not succeed + error SubcallError(); + + error ConsensusUndelegateError(uint64 status, string data); + + error ConsensusDelegateError(uint64 status, string data); + + error ConsensusWithdrawError(uint64 status, string data); + + error AccountsTransferError(uint64 status, string data); /** - * Submit a native message to the Oasis runtime layer + * Submit a native message to the Oasis runtime layer. * * Messages which re-enter the EVM module are forbidden: evm.* * @@ -40,7 +48,7 @@ library Subcall { ); if (!success) { - revert Subcall_Error(); + revert SubcallError(); } (status, data) = abi.decode(tmp, (uint64, bytes)); @@ -75,8 +83,6 @@ library Subcall { ); } - error ConsensusUndelegateError(uint64 status, string data); - /** * Start the undelegation process of the given number of shares from * consensus staking account to runtime account. @@ -104,10 +110,8 @@ library Subcall { } } - error ConsensusDelegateError(uint64 status, string data); - /** - * Delegate native token to consensus level + * Delegate native token to consensus level. * * @param to Consensus address shares are delegated to * @param value Native token amount (in wei) @@ -124,8 +128,6 @@ library Subcall { } } - error ConsensusWithdrawError(uint64 status, string data); - /** * Transfer from an account in this runtime to a consensus staking account. * @@ -144,10 +146,8 @@ library Subcall { } } - error AccountsTransferError(uint64 status, string data); - /** - * Perform a transfer to another account + * Perform a transfer to another account. * * This is equivalent of `payable(to).transfer(value);` * diff --git a/contracts/test/subcall.ts b/contracts/test/subcall.ts index 283afd34..b6f99d95 100644 --- a/contracts/test/subcall.ts +++ b/contracts/test/subcall.ts @@ -79,7 +79,7 @@ describe('Subcall', () => { it('Derive Staking Addresses', async () => { const newKeypair = await contract.generateRandomAddress(); - // Verify @oasisprotocol/client matches Solidity + // Verify `@oasisprotocol/client` matches Solidity. const alice = oasis.signature.NaclSigner.fromSeed( ethers.utils.arrayify(newKeypair.secretKey), 'this key is not important', @@ -92,11 +92,11 @@ describe('Subcall', () => { }); it('accounts.Transfer', async () => { - // Ensure contract has an initial balance + // Ensure contract has an initial balance. const initialBalance = parseEther('1.0'); await ensureBalance(contract, initialBalance, owner); - // transfer balance-1 back to owner, then wait for transaction to be mined + // transfer balance-1 back to owner, then wait for transaction to be mined. const balance = await contract.provider.getBalance(contract.address); let tx = await contract.testSubcall( 'accounts.Transfer', @@ -112,30 +112,30 @@ describe('Subcall', () => { expect(event.status).eq(0); expect(event.data).is.null; - // Ensure contract only has 1 wei left + // Ensure contract only has 1 wei left. expect(await contract.provider.getBalance(contract.address)).eq(1); - // Transfer using the Subcall.accounts_Transfer method + // Transfer using the Subcall.accounts_Transfer method. tx = await contract.testAccountsTransfer(ownerAddr, 1); receipt = await tx.wait(); - // Ensure contract only no wei left + // Ensure contract only no wei left. expect(await contract.provider.getBalance(contract.address)).eq(0); }); it('consensus.Delegate', async () => { - // Ensure contract has an initial balance + // Ensure contract has an initial balance. const initialBalance = parseEther('1.0'); await ensureBalance(contract, initialBalance, owner); - // Delegate 0, ensure balance does not change + // Delegate 0, ensure balance does not change. let tx = await contract.testConsensusDelegate(kp.publicKey, 0); await tx.wait(); expect(await contract.provider.getBalance(contract.address)).eq( initialBalance, ); - // Manually encode & submit consensus.Delegate message + // Manually encode & submit `consensus.Delegate` message. tx = await contract.testSubcall( 'consensus.Delegate', cborg.encode({ @@ -149,13 +149,10 @@ describe('Subcall', () => { const event = decodeResult(receipt); expect(event.status).eq(0); expect(event.data).is.null; - - // Ensure contract only no wei left - //expect(await contract.provider.getBalance(contract.address)).eq(0); }); it('consensus.Undelegate', async () => { - // Ensure contract has an initial balance + // Ensure contract has an initial balance. const initialBalance = parseEther('1.0'); await ensureBalance(contract, initialBalance, owner); @@ -167,7 +164,7 @@ describe('Subcall', () => { }); it('consensus.Withdraw', async () => { - // Ensure contract has an initial balance + // Ensure contract has an initial balance. const initialBalance = parseEther('1.0'); await ensureBalance(contract, initialBalance, owner);