erc20 token swap contracts - swap

I have a question about ERC20 token swap contracts in this code example. (see below: https://solidity-by-example.org/app/erc20/). My question is that since this contract would require giving allowance to the contract beforehand. Doesn't that create a risk that the contract owner could abscond with the tokens before either party can invoke the swap function? In other words, we still require a trusted party to facilitate the transaction?
If so, is there some way of limiting how allowance can be limited (e.g only transfer to certain intended party allowed?)
Thanks
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.0.0/contracts/token/ERC20/IERC20.sol";
/*
How to swap tokens
1. Alice has 100 tokens from AliceCoin, which is a ERC20 token.
2. Bob has 100 tokens from BobCoin, which is also a ERC20 token.
3. Alice and Bob wants to trade 10 AliceCoin for 20 BobCoin.
4. Alice or Bob deploys TokenSwap
5. Alice approves TokenSwap to withdraw 10 tokens from AliceCoin
6. Bob approves TokenSwap to withdraw 20 tokens from BobCoin
7. Alice or Bob calls TokenSwap.swap()
8. Alice and Bob traded tokens successfully.
*/
contract TokenSwap {
IERC20 public token1;
address public owner1;
uint public amount1;
IERC20 public token2;
address public owner2;
uint public amount2;
constructor(
address _token1,
address _owner1,
uint _amount1,
address _token2,
address _owner2,
uint _amount2
) {
token1 = IERC20(_token1);
owner1 = _owner1;
amount1 = _amount1;
token2 = IERC20(_token2);
owner2 = _owner2;
amount2 = _amount2;
}
function swap() public {
require(msg.sender == owner1 || msg.sender == owner2, "Not authorized");
require(
token1.allowance(owner1, address(this)) >= amount1,
"Token 1 allowance too low"
);
require(
token2.allowance(owner2, address(this)) >= amount2,
"Token 2 allowance too low"
);
_safeTransferFrom(token1, owner1, owner2, amount1);
_safeTransferFrom(token2, owner2, owner1, amount2);
}
function _safeTransferFrom(
IERC20 token,
address sender,
address recipient,
uint amount
) private {
bool sent = token.transferFrom(sender, recipient, amount);
require(sent, "Token transfer failed");
}
}

There is no way to misuse the approval in your example above.
However, if the TokenSwap contract included a function that allows an arbitrary token transfer through the contract, then yes - that could be misused.
contract TokenSwap {
address owner = address(0x123);
function withdraw(address token, address victim, uint256 amount) public {
// requires approval from `victim` to `TokenSwap`
IERC20(token).transferFrom(from, owner, amount);
}
}
is there some way of limiting how allowance can be limited (e.g only transfer to certain intended party allowed?)
Unfortunately the ERC-20 standard does not limit the approval to how exactly are the tokens going to be spent. There might be some non-standard solutions, but if you want to implement them, they need to be implemented by both the token contract and your swap contract.

Related

spring-kafka how to set Retry on the stream bean

I saw spring-kafka supports Non-Blocking Retries using #RetryableTopic. I only saw #RetryableTopic is working with #kafkaListener together. But I want the "Retry" on my stream aggregation. How to do that by spring-kafka?
The code example below is about bank transactions (stream) and account balances (state).
Say a bank transaction is like this: move $10 from account 10001 to account 10002.
I have the stream code below using reduce function to -10 from 10001 and +10 to 10002.
And the balance is materialized to state store BALANCE.
if the account 10001 balance is less than 10, the transaction shall not be fulfilled.
But it shall be retried, because a deposit transaction may come in a short period. And after the deposit to 10001, the balance is > 10, then this transaction shall be fulfilled.
Here is my stream bean
#Bean
public KStream<String, BankTransaction> alphaBankKStream(StreamsBuilder streamsBuilder) {
JsonSerde<BankTransaction> valueSerde = new JsonSerde<>(BankTransaction.class);
KStream<String, BankTransaction> stream = streamsBuilder.stream(Topic.TRANSACTION_RAW,
Consumed.with(Serdes.String(), valueSerde));
KStream<String, BankTransaction>[] branches = stream.branch(
(key, value) -> isBalanceEnough(value),
(key, value) -> true /* all other records */
);
branches[0].flatMap((k, v) -> {
List<BankTransactionInternal> txInternals = BankTransactionInternal.splitBankTransaction(v);
List<KeyValue<String, BankTransactionInternal>> result = new LinkedList<>();
result.add(KeyValue.pair(v.getFromAccount(), txInternals.get(0)));
result.add(KeyValue.pair(v.getToAccount(), txInternals.get(1)));
return result;
}).filter((k, v) -> !Constants.EXTERNAL_ACCOUNT.equalsIgnoreCase(k))
.map((k,v) -> KeyValue.pair(k, v.getAmount()))
.groupBy((account, amount) -> account, Grouped.with(Serdes.String(), Serdes.Double()))
.reduce(Double::sum,
Materialized.<String, Double, KeyValueStore<Bytes, byte[]>>as(StateStore.BALANCE).withValueSerde(Serdes.Double()));
return stream;
}
private boolean isBalanceEnough(BankTransaction bankTransaction) {
// read balance from state store BALANCE
return balance >= bankTransaction.amount
}
KStream is outside the scope of Spring for Apache Kafka; spring is only involved with setting up the topology; once it it set up, you are using kafka-streams directly. All the functionality is provided by the topology you set up.
The #RetrybleTopic feature only applies to #KafkaListener (or more specifically the kafka lister containers).

Is there any Microsoft API to get the group Id from channel id?

I have list of channel id of particular group, I want the group Id and name from channel Id.
Is there any such api in Microsoft graph api?
This is not possible with the Graph API. This has been confirmed by Microsoft.
However, when you are working with a channel object and have its id, you usually also have an id of the team it belongs to - for example in the bot framework, it's part of the activity's channelData.
Be prepared not to always have this information, since not all channels of conversations might be Teams channels! But let's assume you're working with Teams channels exclusively.
The id of the team is also the id of its implicitly-generated General channel. You can distinguish that one by the id format 19:…#thread.tacv2 - a normal channel has a hex string whereas the team itself has a longer base62 string and includes a -. Without this id, you're basically lost.
Unfortunately, this team id is still mostly useless. It is not the UUID that you use in the Graph API for the Team resources (at /teams/{team-id}), Channel resources (at /teams/{team-id}/channels/{channel-id}) and Group resources ("Every team is associated with a Microsoft 365 group. The group has the same ID as the team - for example, /groups/{id}/team is the same as /teams/{id}").
Rather, it is the value of the internalId property of the team. It is also the main part of its webUrl. So how to get the groupId/teamId UUID of the team from its internalId string?
The best you can do with the Graph API is to get a list of all teams (either via /groups or via /teams, or at least the teams that you are part of or are otherwise associated with), and then for each team fetch the whole object by id and compare its internalId with the channel id / internal team id that you are looking for.
Unfortunately, /teams?$filter=internalId eq '19%3A…%40thread.tacv2' only gives the response Filter on property 'internalId' is not supported. :-/
However, looking outside of the Graph API, there is the Bot Framework REST API (part of Azure Bot Services), which does provide an undocumented endpoint. I discovered it via this StackOverflow answer, which shows that the Bot Framework SDK for .NET does have a method to fetch this information: FetchTeamDetailsWithHttpMessagesAsync. It takes an internal teamId (which is not distinguished in the documentation) and returns a TeamDetails object which contains an AadGroupId property: "Azure Active Directory (AAD) Group Id for the team." That's the one we want!
And since the code is open-sourced on Github, we can find its implementation:
// Construct URL
var baseUrl = Client.BaseUri.AbsoluteUri;
var url = new System.Uri(new System.Uri(baseUrl + (baseUrl.EndsWith("/", System.StringComparison.InvariantCulture) ? string.Empty : "/")), "v3/teams/{teamId}").ToString();
url = url.Replace("{teamId}", System.Uri.EscapeDataString(teamId));
This works similar in JavaScript/TypeScript, except the SDK doesn't expose the method itself. Turns out I need to look better, it's available as TeamsInfo.getTeamDetails (working from the current TurnContext) or new Teams(client).fetchTeamDetails. Looking at its implementation, it uses
const fetchTeamDetailsOperationSpec: msRest.OperationSpec = {
httpMethod: 'GET',
path: 'v3/teams/{teamId}',
…
};
I'll leave my custom implementation up for posterity still, it has slightly more accurate types:
const SERVICE_URL = 'https://smba.trafficmanager.net/…/';
async function fetchTeamDetails(teamInternalId: string): Promise<TeamDetails> {
const adapter = new BotFrameworkAdapter({
…
});
const client = adapter.createConnectorClient(SERVICE_URL);
const response = await client.sendRequest({
method: 'GET',
url: `${SERVICE_URL}/v3/teams/${encodeURIComponent(teamInternalId)}`,
});
return response.parsedBody as TeamDetails;
}
/** Details related to a team. Modelled after https://learn.microsoft.com/en-us/dotnet/api/microsoft.bot.schema.teams.teamdetails (which is lacking `tenantId` though) */
interface TeamDetails {
/** Unique identifier representing a team. */
id: string;
/** Tenant Id for the team. */
tenantId: string; // UUID
/** Azure Active Directory (AAD) Group Id for the team. */
aadGroupId: string; // UUID
/** type of the team */
type: 'standard' | 'sharedChannel' | 'privateChannel';
/** Name of team. */
name: string;
/** Number of channels in the team. */
channelCount: number;
/** Number of members in the team. */
memberCount: number;
}
Reverse is possible.
If you have Group/team id, can get channels. But no API to get Group id based on Channel id.

How Do I Store Extra Private Information Related To A State?

I have a simple flow that creates a state between a buyer and seller and obviously each side can see everything on the state.
However, I now have a requirement that the buyer wants to store the user that processed the transaction for auditing and reporting purposes.
The user in this case is not a node or an account but a user that has logged in to the application and been authorised via Active Directory.
I could just add the user name to the state as a String but that would expose private data to the seller.
An alternative would be to obfuscate the name in some way but I would rather store the information in a separate table outside the state and only in the buyers vault.
How do I do this and is there a sample that demonstrates it?
You can create a second output state, which is used in the same transaction, but has only the token issuer as participant. Then of course, it is up to you to make the link between the "issued state" and the "recorder state", it depends on what you will store inside the latter.
Let's make an example of a fungible token issuance from Node1 to Node2. You could create a "issuance recorder state" that aims at recording something on Node1's vault only, like so (note the list of participants):
// the "recorder" state visible only in Node1's vault
#BelongsToContract(IssuanceRecordContract::class)
class IssuanceRecord(
val holder: AbstractParty,
val amount: Amount<IssuedTokenType>
) : ContractState {
override val participants: List<AbstractParty>
get() = listOf(amount.token.issuer)
}
and then you could pass it to the same TransactionBuilder that you are using to issue the fungible token (which instead has both parties in the list of participants), like so:
// This is from the Issuanflow launched from Node1
#Suspendable
override fun call(): String {
...
...
// Create the FungibleToken (issuedToken is an IssuedTokenType created before)
val tokenCoin = FungibleToken(Amount(volume.toLong(), issuedToken), holderAnonymousParty)
// create the "recorder" output state visible only to Node1
val issuanceRecord = IssuanceRecord(holderAnonymousParty, tokenCoin.amount)
// create the Transaction Builder passing the "recorder" output state
val transactionBuilder = TransactionBuilder(notary).apply {
addOutputState(issuanceRecord, IssuanceRecordContract.ID)
addCommand(Command(IssuanceRecordContract.Commands.Issue(), ourIdentity.owningKey))
}
// Issue the token passing the transactionBuilder and the fungible token
addIssueTokens(transactionBuilder, tokenCoin)
// collect signatures
// verify transaction
// FinalityFlow (fundamental to make this work in Node1)
}
This way, I think, the recorder states will be atomically stored in Node1's vault. If something happens, the transaction will be not successful for both output states.

Inaccessible Cash State

I am running into an issue with cash states. Basically I have a node that issues itself money and is unable to access an existing state to use for payment/anything. Let’s say this state is 5 dollars, if I issue 10 more, both rpcOps and the servicehub getCashBalances will say that I have 15 dollars. However, any cash flows that try to use more than 10 dollars will tell me I don’t have sufficient balance.
I’ve set up api endpoints for the node to even just exit the cash but it will say that I’m exiting more than I have. When I query the vault with QueryCriteria.VaultQueryCriteria(Vault.StateStatus.UNCONSUMED), I can see the state is there, and there doesn’t seem to be anything that differentiates the inaccessible state from any subsequent accessible states.
Could there be anything I’m overlooking here? The issuers are the same and the owners are hashed but should be the same, as well.
Updated with command / code:
fun selfIssueTime(#QueryParam(value = "amount") amount: Long,
#QueryParam(value = "currency") currency: String): Response {
// 1. Prepare issue request.
val issueAmount = Amount(amount.toLong() * 100, Currency.getInstance(currency))
val notary = rpcOps.notaryIdentities().firstOrNull() ?: throw IllegalStateException("Could not find a notary.")
val issueRef = OpaqueBytes.of(0)
val issueRequest = CashIssueFlow.IssueRequest(issueAmount, issueRef, notary)
val self = myIdentity
// 2. Start flow and wait for response.
val (status, message) = try {
val flowHandle = rpcOps.startFlowDynamic(
CashIssueFlow::class.java,
issueRequest
)
flowHandle.use { it.returnValue.getOrThrow() }
CREATED to "$issueAmount issued to $self."
} catch (e: Exception) {
BAD_REQUEST to e.message
}
// 3. Return the response.
return Response.status(status).entity(message).build()
}
I believe this is fixed via the later version of Corda finance jars. We have developed a couple more CorDapps samples using the currency class, and we did not run into any issue. For example: https://github.com/corda/samples-java/blob/master/Tokens/dollartohousetoken/workflows/src/main/java/net/corda/examples/dollartohousetoken/flows/FiatCurrencyIssueFlow.java#L39
Further more, with the release of the Corda TokenSDk, Currency on Corda has actually a new way to get issue, transfer and redeem. This is done by:
/* Create an instance of the fiat currency token */
TokenType token = FiatCurrency.Companion.getInstance(currency);
/* Create an instance of IssuedTokenType for the fiat currency */
IssuedTokenType issuedTokenType = new IssuedTokenType(getOurIdentity(), token);
/* Create an instance of FungibleToken for the fiat currency to be issued */
FungibleToken fungibleToken = new FungibleToken(new Amount<>(amount, issuedTokenType), recipient, null);
/* Issue the required amount of the token to the recipient */
return subFlow(new IssueTokens(ImmutableList.of(fungibleToken), ImmutableList.of(recipient)));

Which are the security procedures which we need to consider when we collect transaction signatures?

Consider the following case:
node A builds and signs a TX which is sent to B for signing.
class FlowA(val otherParty: Party) : FlowLogic<SignedTransaction>() {
#Suspendable
override fun call(): SignedTransaction {
val notary = serviceHub.networkMapCache.getNotary()
val builder = TransactionBuilder(notary)
// ... add some commands and states
val stx = serviceHub.signInitialTransaction(builder)
val session = initiateFlow(otherParty)
subFlow(SendTransactionFlow(session, stx))
return session.sendAndReceive<SignedTransaction>(stx).unwrap {
it.id == stx.id // is it enough?
it
}
}
}
class FlowB(val session: FlowSession) : FlowLogic<Unit>() {
#Suspendable
override fun call() {
subFlow(ReceiveTransactionFlow(session, false))
val stx = session.receive<SignedTransaction>().unwrap {
val ledgerTx = it.toLedgerTransaction(serviceHub, false)
ledgerTx.commandsOfType<SomeContract.Commands.SomeCommand>().single()
ledgerTx.verify() // is it enough?
}
}
}
Is it secure to check only the id of the transaction from the sender side once we received the full signed transaction?
I've read in the doc that id is the root Merkle tree built by using transaction's components, so if the otherParty change something the id would be different, correct?
From the receiver side, is it secure to check which commands are present in the transaction so we are sure that the contract relative to that command is run by means of verify?
On receiving a transaction, you will be able to interrogate all the inputs, outputs and commands.
Typically, the interrogation would happen inside the contracts verify method.
Yes, if anything were to change the root ID would indeed change. Note that it's not possible to change anything within a transaction once it has been signed.
Other things you could look for is the presence of the transaction being notarised.
It depends on what your contract needs to do, but yes, you would check which commands are present within a transaction e.g. in the issuance of a security, you might check that only one issue command is present.
You can also check the commands have received the required counterparty signatures.
If you would like to discuss in person, feel free to join one of office hour sessions - https://www.corda.net/support/technical-office-hours/

Resources